Popular Searches
Popular Course Categories
Popular Courses

Implementing Communication Between Microservices

Implementing Communication Between Microservices

Microservices Development Using Spring Boot

Implementing Communication Between Microservices

Course: Advanced Java Training

Topic: Implementing Communication Between Microservices

Course Link: Advanced Java Training – JustAcademy

Demo Link: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Microservices Communication

Microservices architecture is a software development approach in which an application is divided into multiple small, independent services. Each service focuses on a specific business capability and can be developed, deployed, scaled, and maintained independently.

Since microservices operate as separate processes, they need mechanisms to exchange information and coordinate business operations. This process is called microservices communication.

For example, in an e-commerce application, the Order Service may communicate with the Payment Service to process a payment, the Inventory Service to reserve products, and the Notification Service to send order confirmation messages.

Why Is Microservices Communication Important?

  • Allows independent services to exchange business data.
  • Supports distributed business workflows.
  • Enables integration between services developed by different teams.
  • Improves scalability by allowing individual services to operate independently.
  • Supports synchronous requests and asynchronous event processing.
  • Helps build modular, maintainable, and resilient applications.

2. Understanding Different Communication Patterns

Microservices generally communicate through synchronous or asynchronous communication patterns. The appropriate approach depends on response-time requirements, reliability, service dependencies, and business needs.

Communication Pattern Description Common Technologies Suitable Use Case
Synchronous The caller waits for the response from the receiving service. REST, HTTP, OpenFeign Retrieving customer or product details.
Asynchronous The sender publishes a message or event without waiting for the consumer to finish processing it. RabbitMQ, Apache Kafka Sending notifications or processing order events.
Request-Response A service sends a request and receives a corresponding response. REST, gRPC Checking payment status.
Event-Driven A service publishes an event that interested services consume. Kafka, RabbitMQ Updating inventory after an order is placed.
Service-to-Service One service directly invokes another service's API. REST, OpenFeign, WebClient Fetching shipping estimates.

3. Synchronous Communication Between Microservices

Synchronous communication occurs when one service sends a request to another service and waits for its response before continuing its operation.

REST APIs are widely used for synchronous communication because they use standard HTTP methods and support JSON-based data exchange.

Example Scenario

Suppose the Order Service needs customer information before creating an order. It sends an HTTP request to the Customer Service. The Customer Service processes the request and returns customer details.

Common HTTP Methods

HTTP Method Purpose Example
GET Retrieve information. Get customer details.
POST Create a resource or initiate an operation. Create a new order.
PUT Replace an existing resource. Update customer information.
PATCH Partially update a resource. Update an order status.
DELETE Remove a resource. Delete an address.

Advantages of Synchronous Communication

  • Easy to understand and implement.
  • Provides an immediate response to the caller.
  • Works well for operations requiring current information.
  • Integrates easily with Spring Boot REST controllers.

Limitations of Synchronous Communication

  • The caller may wait if the receiving service is slow.
  • Service failures can affect dependent services.
  • Excessive service-to-service calls can increase latency.
  • Timeouts and retry policies must be configured carefully.

4. Creating a REST API Using Spring Boot

Spring Boot simplifies REST API development through annotations such as @RestController, @GetMapping, and @RequestMapping.

Step 1: Create the Customer Service

Create a Spring Boot project with the Spring Web dependency. Add the following controller class:

package com.example.customerservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController {

    @GetMapping("/customers/{id}")
    public Customer getCustomer(@PathVariable Long id) {
        return new Customer(id, "Rahul Sharma",
                            "[email protected]");
    }
}

record Customer(Long id, String name, String email) {}

This example exposes a GET endpoint that returns customer information. The sample returns fixed data to demonstrate communication; a production service would normally retrieve the customer from a database and handle missing records.

Step 2: Configure the Service Port

In the Customer Service's application.properties file, configure its port:

server.port=8081

Step 3: Test the Customer API

Start the Customer Service and send a GET request to:

GET http://localhost:8081/customers/1

Example JSON response:

{
  "id": 1,
  "name": "Rahul Sharma",
  "email": "[email protected]"
}

5. Calling Another Microservice Using RestClient

Spring's RestClient provides a synchronous, fluent API for making HTTP requests. It is available in modern Spring Framework versions, including Spring Framework 6.1 and later.

Step 1: Create the Order Service

Create another Spring Boot application with the Spring Web dependency. Configure its port:

server.port=8082

Step 2: Configure RestClient

package com.example.orderservice;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {

    @Bean
    RestClient customerRestClient(RestClient.Builder builder) {
        return builder
                .baseUrl("http://localhost:8081")
                .build();
    }
}

Step 3: Create a Service That Calls the Customer API

package com.example.orderservice;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class CustomerApiService {

    private final RestClient restClient;

    public CustomerApiService(RestClient customerRestClient) {
        this.restClient = customerRestClient;
    }

    public Customer getCustomer(Long id) {
        return restClient.get()
                .uri("/customers/{id}", id)
                .retrieve()
                .body(Customer.class);
    }

    public record Customer(Long id, String name, String email) {}
}

Step 4: Expose an Order Service Endpoint

package com.example.orderservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    private final CustomerApiService customerApiService;

    public OrderController(CustomerApiService customerApiService) {
        this.customerApiService = customerApiService;
    }

    @GetMapping("/orders/customer/{id}")
    public CustomerApiService.Customer getCustomerForOrder(
            @PathVariable Long id) {
        return customerApiService.getCustomer(id);
    }
}

Now send a request to http://localhost:8082/orders/customer/1. The Order Service calls the Customer Service and returns its response.

Important: This example demonstrates the communication flow, not a complete production-ready order implementation. In a real application, validate identifiers, handle HTTP errors, configure timeouts, and avoid exposing internal service details.

6. Using WebClient for Non-Blocking Communication

WebClient is a reactive HTTP client provided by Spring WebFlux. It supports asynchronous and non-blocking request processing when used with a reactive execution model.

Example

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class CustomerWebClientService {

    private final WebClient webClient;

    public CustomerWebClientService(WebClient.Builder builder) {
        this.webClient = builder
                .baseUrl("http://localhost:8081")
                .build();
    }

    public Mono<Customer> getCustomer(Long id) {
        return webClient.get()
                .uri("/customers/{id}", id)
                .retrieve()
                .bodyToMono(Customer.class);
    }

    public record Customer(Long id, String name, String email) {}
}

The returned Mono<Customer> represents a future asynchronous result that may contain one customer. The calling application should compose the reactive pipeline instead of blocking unnecessarily.

RestClient vs. WebClient

Feature RestClient WebClient
Communication style Synchronous Supports reactive, non-blocking requests
Programming model Direct return values Mono and Flux for reactive flows
Best suited for Traditional Spring MVC applications Reactive applications and streaming scenarios
Complexity Generally simpler Requires understanding reactive programming

7. Using OpenFeign for Declarative REST Communication

OpenFeign allows developers to define HTTP clients through Java interfaces. Instead of writing the HTTP request logic manually, developers declare the remote endpoint using annotations.

Step 1: Add the Dependency

For Spring Cloud projects, add the Spring Cloud OpenFeign starter and use a Spring Cloud release train compatible with the selected Spring Boot version.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Step 2: Enable Feign Clients

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

Step 3: Define a Client Interface

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(
    name = "customer-service",
    url = "${services.customer.url}"
)
public interface CustomerClient {

    @GetMapping("/customers/{id}")
    Customer getCustomer(@PathVariable("id") Long id);
}

record Customer(Long id, String name, String email) {}

Configure the URL in application.properties:

services.customer.url=http://localhost:8081

Inject CustomerClient into a service class and call getCustomer(id) like a regular Java method. Feign handles the HTTP request mapping.

Note: The URL above is suitable for a local example. In a distributed deployment, service discovery or environment-specific configuration may be used instead.

8. Service Discovery and Registration

In a dynamic microservices environment, service instances may start, stop, or change network addresses. Hardcoding every service URL becomes difficult to maintain.

Service discovery allows a service to locate another service through a registry or discovery mechanism.

How Service Discovery Works

  1. A microservice starts and registers its network location with the discovery system.
  2. The registry maintains information about available service instances.
  3. A calling service requests an instance by its logical service name.
  4. The discovery client or load balancer selects an available instance.
  5. The request is sent to the selected service instance.

Common Service Discovery Technologies

  • Netflix Eureka: A service registry commonly used in Spring Cloud environments.
  • Consul: Provides service discovery and health-check functionality.
  • Kubernetes Services: Provide stable service names and network access within Kubernetes clusters.

With service discovery, a Feign client can often use a logical service name without a fixed URL, provided that discovery and load-balancing dependencies are correctly configured.

9. Asynchronous Communication Between Microservices

Asynchronous communication allows a service to send a message or publish an event without waiting for the receiving service to finish processing it. A message broker can temporarily store messages and deliver them to consumers.

Example: Order Placement

When a customer places an order, the Order Service creates the order and publishes an OrderCreated event. Other services can consume that event to reserve inventory, send a notification, or start a shipping workflow.

Benefits of Asynchronous Communication

  • Reduces direct runtime dependencies between services.
  • Supports background processing and high-volume workloads.
  • Can improve resilience when consumers are temporarily unavailable.
  • Allows multiple consumers to react to the same business event.
  • Helps separate the responsibilities of individual services.

Potential Challenges

  • Messages may be delivered more than once.
  • Processing can occur after a delay.
  • Consumers may process events in an unexpected order unless ordering is designed explicitly.
  • Debugging distributed workflows can be more difficult.
  • Message schema compatibility and dead-letter handling require attention.

10. Implementing Messaging with RabbitMQ

RabbitMQ is a message broker that supports message queues, exchanges, routing keys, acknowledgments, and several messaging patterns. Spring AMQP provides integration between RabbitMQ and Spring applications.

Typical RabbitMQ Workflow

  1. The producer sends a message to an exchange.
  2. The exchange routes the message according to its binding rules.
  3. The message is placed in an appropriate queue.
  4. A consumer receives and processes the message.
  5. The broker acknowledges successful processing according to the configured acknowledgment model.

Example Producer

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderEventPublisher {

    private final RabbitTemplate rabbitTemplate;

    public OrderEventPublisher(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void publishOrderCreated(Long orderId) {
        rabbitTemplate.convertAndSend(
            "orders.exchange",
            "orders.created",
            "Order created: " + orderId
        );
    }
}

This producer sends a message to an exchange named orders.exchange using the routing key orders.created. The exchange, binding, queue, RabbitMQ connection, and message serialization must be configured separately.

Example Consumer

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventConsumer {

    @RabbitListener(queues = "orders.queue")
    public void consumeOrderCreated(String message) {
        System.out.println("Received event: " + message);
    }
}

The consumer listens to orders.queue. For this example to work, the queue must be declared and bound to the exchange using the appropriate routing key.

11. Implementing Event Streaming with Apache Kafka

Apache Kafka is a distributed event-streaming platform used to publish, store, and consume streams of records. It is particularly useful when multiple services need to process business events or when event retention and replay are important.

Important Kafka Concepts

Concept Meaning
Producer Publishes records to Kafka topics.
Consumer Reads records from Kafka topics.
Topic A named stream of records.
Partition A subdivision of a topic that supports parallelism and ordering within a partition.
Consumer Group A group of consumers that coordinate consumption of topic partitions.
Offset A record position within a partition.

Example Kafka Producer with Spring Kafka

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderKafkaPublisher {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public OrderKafkaPublisher(
            KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publish(Long orderId) {
        kafkaTemplate.send(
            "order-events",
            orderId.toString(),
            "Order created: " + orderId
        );
    }
}

Example Kafka Consumer

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderKafkaConsumer {

    @KafkaListener(
        topics = "order-events",
        groupId = "notification-service"
    )
    public void consume(String message) {
        System.out.println("Processing event: " + message);
    }
}

To run these examples, configure a Kafka broker, the Spring Kafka dependency, topic settings, and appropriate serializers and deserializers. Production consumers should also define error handling, retry behavior, and offset-management policies.

12. REST vs. RabbitMQ vs. Kafka

Feature REST API RabbitMQ Apache Kafka
Primary purpose Request-response APIs Message delivery and queuing Event streaming and distributed event processing
Communication Usually synchronous Usually asynchronous Usually asynchronous
Message storage Not inherently a message store Queues can store messages until delivery or expiry Records are retained according to topic configuration
Typical use case Read customer profile Send an email task Stream order and transaction events
Key consideration Timeouts and API availability Queue configuration and acknowledgment Partitioning, offsets, retention, and consumer groups

These technologies are complementary. A real-world system may use REST for queries, RabbitMQ for task queues, and Kafka for event streaming.

13. Data Transfer Objects (DTOs)

A Data Transfer Object (DTO) is an object used to transfer selected data between application layers or services. DTOs help prevent internal database entities from becoming part of a public API contract.

Example DTO

public record OrderResponse(
    Long orderId,
    String status,
    double totalAmount
) {}

DTOs should contain the information required by the receiving service rather than every field in the source database model.

DTO Best Practices

  • Keep API request and response structures clear.
  • Validate incoming data before processing it.
  • Avoid exposing sensitive fields.
  • Use explicit data types and consistent naming.
  • Plan API versioning when contracts change.
  • Use stable event schemas for asynchronous messages.

14. Handling Errors in Service-to-Service Communication

Network failures, unavailable services, invalid requests, and processing errors are common in distributed applications. Error handling should be designed as part of the communication strategy.

Common HTTP Status Codes

Status Code Meaning Typical Handling
200 OK Request completed successfully. Process the returned data.
201 Created A resource was created successfully. Use the returned resource information.
400 Bad Request The request is invalid. Correct the request; do not blindly retry.
401 Unauthorized Authentication is missing or invalid. Check credentials or token configuration.
403 Forbidden The caller lacks permission. Review access policies.
404 Not Found The requested resource does not exist. Handle missing resources appropriately.
429 Too Many Requests The caller exceeded a rate limit. Respect rate limits and retry guidance.
500 Internal Server Error The service encountered an unexpected error. Log safely and apply appropriate recovery.
503 Service Unavailable The service is temporarily unavailable. Use bounded retries or a fallback if appropriate.

Example: Handling REST Client Errors

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientResponseException;

@Service
public class SafeCustomerService {

    private final RestClient restClient;

    public SafeCustomerService(RestClient.Builder builder) {
        this.restClient = builder
                .baseUrl("http://localhost:8081")
                .build();
    }

    public String getCustomerName(Long id) {
        try {
            Customer customer = restClient.get()
                    .uri("/customers/{id}", id)
                    .retrieve()
                    .body(Customer.class);

            return customer == null
                    ? "Customer response was empty"
                    : customer.name();
        } catch (RestClientResponseException ex) {
            // Log status and a safe diagnostic message.
            return "Customer service request failed with status "
                    + ex.getStatusCode().value();
        }
    }

    public record Customer(Long id, String name, String email) {}
}

This simplified example demonstrates HTTP response error handling. In production code, prefer typed exceptions or domain-level errors over returning generic strings, and avoid exposing internal failure details to clients.

15. Timeouts, Retries, and Circuit Breakers

Timeouts

A timeout defines how long a service waits for a response or connection. Without appropriate timeouts, a slow dependency can consume application resources for too long.

Retries

Retries attempt an operation again after a failure. They should be bounded, use suitable backoff delays, and be applied only when retrying is safe. Retrying non-idempotent operations can create duplicate orders or payments.

Circuit Breakers

A circuit breaker temporarily stops requests to a failing dependency. It generally uses three states:

  • Closed: Requests are allowed and failures are monitored.
  • Open: Requests are rejected or redirected without repeatedly calling the failing service.
  • Half-open: A limited number of test requests determine whether the service has recovered.

Resilience4j is a common library for implementing circuit breakers, retries, rate limiters, and bulkheads in Java applications.

Recommended Resilience Practices

  • Set connection and response timeouts appropriate to the operation.
  • Use exponential backoff and jitter for suitable retry scenarios.
  • Apply circuit breakers to unstable remote dependencies.
  • Use bulkheads to isolate resource consumption between workloads.
  • Provide meaningful fallback behavior where the business process permits it.
  • Monitor failure rates, latency, and retry counts.

16. Load Balancing Between Microservices

Load balancing distributes incoming requests across multiple service instances. It helps prevent one instance from receiving all the traffic and supports scaling and availability.

Types of Load Balancing

  • Client-side load balancing: The client selects an instance from available service instances.
  • Server-side load balancing: A proxy or load balancer routes traffic to a service instance.
  • Round-robin: Requests are distributed sequentially among available instances.
  • Least-connections: Traffic is directed toward an instance with fewer active connections, where supported.

Load balancing should be combined with health checks, sensible timeouts, and appropriate connection management. The actual strategy depends on the infrastructure and client library.

17. API Gateway and Microservices Communication

An API Gateway acts as an entry point for external clients. Instead of requiring clients to call multiple internal services directly, the gateway routes requests to the appropriate backend services.

Responsibilities of an API Gateway

  • Request routing to appropriate microservices.
  • Authentication and authorization integration.
  • Rate limiting and request filtering.
  • Centralized access logging and monitoring.
  • Request or response transformation where necessary.
  • Handling cross-cutting concerns shared by multiple services.

Example Architecture

                Web / Mobile Client
                         |
                         v
                    API Gateway
                         |
          +--------------+--------------+
          |              |              |
          v              v              v
     Order Service  Customer Service  Product Service
          |              |              |
          v              v              v
       Database        Database        Database

An API Gateway does not eliminate the need for secure service-to-service communication. Internal services should still validate identities and permissions where required.

18. Security in Microservices Communication

Microservices exchange business and potentially sensitive information, so both the communication channel and the receiving service must be protected.

Important Security Practices

  • HTTPS/TLS: Encrypt network traffic between clients and services.
  • OAuth 2.0: Supports delegated authorization through access tokens.
  • OpenID Connect: Adds an identity layer on top of OAuth 2.0.
  • JWT validation: Validate token signature, issuer, audience, expiry, and relevant claims.
  • Least privilege: Give each service only the permissions it needs.
  • Secrets management: Store credentials securely rather than hardcoding them.
  • Input validation: Reject malformed or unauthorized requests.
  • Audit logging: Record relevant security events without logging passwords, tokens, or sensitive payloads.

For service-to-service authentication, use an approach suited to the environment, such as OAuth 2.0 client credentials, workload identity, or mutual TLS.

19. Distributed Transactions and the Saga Pattern

In a microservices architecture, each service often manages its own database. A single business workflow may therefore involve multiple independent transactions.

For example, placing an order may require the Order Service to create an order, the Payment Service to authorize payment, and the Inventory Service to reserve stock. A failure in one step can require corrective actions in other services.

What Is the Saga Pattern?

The Saga pattern coordinates a sequence of local transactions. If a later step fails, the system can execute compensating actions to reverse or offset earlier business operations.

Example Saga Workflow

  1. Create a pending order.
  2. Reserve inventory.
  3. Authorize payment.
  4. Confirm the order if all required steps succeed.
  5. If payment fails, release the inventory and cancel or mark the order as failed.

Types of Saga Coordination

  • Choreography: Services react to events published by other services, without a central coordinator controlling every step.
  • Orchestration: A coordinator directs the workflow and decides which action should happen next.

A Saga does not provide a single ACID transaction across all participating databases. It relies on local transactions, workflow state, retries, and compensating actions.

20. Idempotency and Duplicate Message Handling

Idempotency means that repeating an operation with the same identifier or input does not create an unintended additional effect. It is particularly important when networks fail and requests or messages are retried.

Example

If a payment request is received twice because the caller did not receive the first response, an idempotency key can help the Payment Service recognize the duplicate and return the existing result rather than charging the customer twice.

Best Practices

  • Assign unique identifiers to business operations and events.
  • Store processed message IDs when duplicate detection is required.
  • Enforce database uniqueness constraints where appropriate.
  • Make consumers safe to retry.
  • Define how duplicate events affect business state.
  • Do not assume that a broker or HTTP client guarantees exactly-once business execution.

21. Observability and Distributed Tracing

Because a request can travel through several services, debugging requires visibility into the complete communication path.

Three Main Observability Signals

  • Logs: Record events, errors, and useful diagnostic information.
  • Metrics: Track request volume, error rate, latency, queue depth, and resource usage.
  • Traces: Show how a request travels across services and where time is spent.

Useful Tools

Tool Purpose
Micrometer Application metrics instrumentation.
Prometheus Metrics collection and querying.
Grafana Dashboards and visualization.
OpenTelemetry Vendor-neutral instrumentation for traces, metrics, and logs.
Jaeger Distributed tracing and trace analysis.
ELK Stack Centralized log collection and analysis using Elasticsearch, Logstash, and Kibana.

Use correlation IDs or trace context to connect logs and traces across service boundaries. Avoid placing sensitive personal or authentication data in telemetry.

22. Common Microservices Communication Problems and Solutions

Problem Possible Cause Recommended Solution
Connection refused Target service is stopped or the address is incorrect. Check service health, ports, DNS, and configuration.
Request timeout Slow processing or network delay. Configure timeouts, inspect traces, and optimize the dependency.
HTTP 404 Incorrect endpoint or missing resource. Verify the route, request parameters, and resource existence.
HTTP 401 or 403 Invalid credentials or insufficient permissions. Review token validation and access policies.
Duplicate processing Retries or repeated message delivery. Use idempotency keys and deduplication.
Lost or unprocessed events Incorrect broker configuration or consumer failure. Review acknowledgments, offsets, retries, and dead-letter handling.
High latency Excessive service calls or resource contention. Reduce unnecessary calls, tune connection pools, and measure bottlenecks.
Inconsistent business state Partial failure across multiple services. Use a Saga workflow, reconciliation, and compensating actions.

23. Best Practices for Implementing Microservices Communication

  • Choose synchronous communication for immediate responses and asynchronous messaging for decoupled workflows.
  • Keep service contracts clear, stable, and versioned when necessary.
  • Set connection and response timeouts explicitly.
  • Apply bounded retries only to operations that are safe to repeat.
  • Use circuit breakers and bulkheads for appropriate failure scenarios.
  • Validate every incoming request and protect sensitive data.
  • Use DTOs rather than exposing internal persistence entities.
  • Implement idempotent consumers and duplicate-message protection.
  • Use health checks, metrics, centralized logging, and distributed tracing.
  • Secure internal service traffic and apply least-privilege access.
  • Keep configuration external to application code.
  • Test service failures, delayed responses, duplicate events, and recovery scenarios.

24. Practical Project: Order Management Microservices

Build a small order management system that demonstrates multiple communication approaches.

Project Requirements

  • Customer Service: Manages customer information and exposes REST APIs.
  • Product Service: Provides product details, availability, and prices.
  • Order Service: Creates orders and communicates with Customer and Product Services.
  • Payment Service: Simulates payment authorization and publishes payment status events.
  • Notification Service: Consumes order or payment events and records simulated notifications.
  • API Gateway: Routes external client requests to the relevant services.
  • Message Broker: RabbitMQ or Kafka for asynchronous event processing.

Implementation Steps

  1. Create independent Spring Boot projects for each service.
  2. Define DTOs and REST endpoints for customer and product operations.
  3. Configure Order Service to call the required APIs using RestClient or OpenFeign.
  4. Add request validation and meaningful HTTP error responses.
  5. Publish an order-created event after the order transaction succeeds.
  6. Configure a message consumer for notifications or payment-status processing.
  7. Implement safe retry behavior and duplicate-event protection.
  8. Add health checks, logging, and metrics.
  9. Test successful requests, service downtime, timeouts, and message redelivery.
  10. Document the architecture, APIs, setup instructions, and test results.

Expected Learning Outcomes

  • Understand direct REST communication between independent services.
  • Implement synchronous HTTP clients in Spring Boot.
  • Understand event-driven communication through a broker.
  • Handle failures, retries, and duplicate processing.
  • Apply security and observability principles.
  • Build a project suitable for a Java backend portfolio.

25. Practice Exercises

  1. Create a Customer Service that exposes a REST endpoint to retrieve customer information.
  2. Create a Product Service and call it from the Order Service.
  3. Implement the same service call using both RestClient and OpenFeign.
  4. Create a RabbitMQ queue to process order notifications asynchronously.
  5. Publish an order event to Kafka and consume it from another service.
  6. Simulate a timeout and implement an appropriate error response.
  7. Configure a circuit breaker for a service that frequently fails.
  8. Implement an idempotency key for a simulated payment operation.
  9. Trace a request across two or more services using correlation IDs.
  10. Document when you would select REST, RabbitMQ, or Kafka for a given business requirement.

26. Interview Questions and Answers

Q1. What is microservices communication?

Answer: It is the exchange of requests, responses, messages, or events between independently deployed services to complete business operations.

Q2. What is the difference between synchronous and asynchronous communication?

Answer: In synchronous communication, the caller waits for a response. In asynchronous communication, the sender can continue after sending a message or publishing an event, while processing occurs separately.

Q3. Why are REST APIs commonly used in microservices?

Answer: REST APIs use standard HTTP methods, are widely supported, and allow services to exchange structured data without requiring the same programming language.

Q4. What is OpenFeign?

Answer: OpenFeign is a declarative HTTP client that lets developers define remote API calls through annotated Java interfaces.

Q5. What is the difference between RestClient and WebClient?

Answer: RestClient is designed for synchronous HTTP requests, while WebClient supports reactive, non-blocking HTTP communication.

Q6. What is service discovery?

Answer: Service discovery enables a service to find available instances of another service through a registry or platform-provided naming mechanism.

Q7. What is a message broker?

Answer: A message broker receives messages from producers and routes them to queues or consumers according to its configuration.

Q8. When would you use RabbitMQ instead of REST?

Answer: RabbitMQ is useful when tasks should be queued and processed asynchronously, especially when producers and consumers should not depend on being available at the same time.

Q9. What is Apache Kafka used for?

Answer: Kafka is used for distributed event streaming, durable event retention, and processing high-volume streams of records across consumers.

Q10. What is a circuit breaker?

Answer: A circuit breaker temporarily prevents calls to a failing dependency to reduce repeated failures and allow recovery.

Q11. Why are idempotent operations important?

Answer: They help ensure that retries or duplicate messages do not cause unintended repeated business effects, such as duplicate payments.

Q12. What is the Saga pattern?

Answer: Saga coordinates a business workflow across multiple local transactions and uses compensating actions when a later step fails.

Q13. What is distributed tracing?

Answer: Distributed tracing follows a request across multiple services and helps identify latency, failures, and dependency relationships.

Q14. What is an API Gateway?

Answer: An API Gateway provides a central entry point for external clients and handles routing and selected cross-cutting concerns.

Q15. How can communication between microservices be secured?

Answer: Use TLS, appropriate service authentication, authorization checks, secure secret management, least-privilege permissions, and careful token validation.

27. Quick Revision Checklist

  • Understand synchronous and asynchronous communication.
  • Know how to expose REST APIs with Spring Boot.
  • Know how to call remote APIs using RestClient, WebClient, and OpenFeign.
  • Understand service discovery and load balancing.
  • Understand RabbitMQ exchanges, queues, routing keys, and consumers.
  • Understand Kafka topics, partitions, producers, consumers, and offsets.
  • Know how to handle timeouts, retries, circuit breakers, and bulkheads.
  • Understand DTOs, API contracts, and versioning.
  • Know the basics of security, observability, and distributed tracing.
  • Understand idempotency and Saga-based workflows.
  • Build and test a small multi-service application.

28. Conclusion

Implementing communication between microservices is a fundamental skill in modern Java backend development. REST APIs and HTTP clients are useful for direct request-response interactions, while RabbitMQ and Kafka support asynchronous messaging and event-driven workflows.

Reliable communication also requires appropriate timeouts, error handling, retries, security, idempotency, observability, and strategies for managing distributed business transactions. Combining these practices helps developers build services that are maintainable, scalable, and resilient.

To learn more about Java development and related training, explore the following JustAcademy resources:

Previous Next
whatsapp