Microservices & API Communication
1. Breaking Monolith into Microservices
1.1 What is a Monolith?
A single, tightly coupled application where:
● All modules are packaged together
● Shared database
● One deployment unit
● Any change requires redeployment of entire application
Problems:
● Hard to scale individual modules
● Slow deployments
● Hard to manage large teams
● High risk of regression
● Difficulty introducing new technologies
1.2 Why Companies Move to Microservices?
Microservices give:
● Independent deployment
● Independent scaling
● Technology freedom
● Better separation of concerns
● Fault isolation
● Faster development cycles
● CI/CD compatibility
Companies using Microservices: Netflix, Amazon, Uber, Flipkart, PayTM, Zomato,
Swiggy, BYJU’S
1.3 Strategy to Break Monolith
Microservices are extracted based on business capabilities, not controllers.
Example structure:
● User Service
● Product Service
● Payment Service
● Inventory Service
● Order Service
Approach (Strangler Pattern):
1. Identify domains
2. Extract service boundaries
3. Introduce API gateway
4. Slowly divert monolith traffic to microservices
5. Sunset old monolith modules
2. Microservices Architecture Overview
2.1 Key Components in Architecture
Core building blocks:
✔ API Gateway
✔ Service Registry
✔ Microservices (Business logic)
✔ Database per service
✔ Load Balancer
✔ Circuit Breaker / Resilience4j
✔ Messaging (Kafka/RabbitMQ)
✔ Config Server
✔ Observability (Zipkin/Sleuth/ELK)
2.2 Patterns in Microservices
● API Gateway
● Service Registry
● Client-side Load Balancing
● Circuit Breaker
● Retry + Timeout
● Saga
● Outbox
● Bulkhead
● BFF pattern
● Distributed Logging
● Centralized Config
3. Service Registry (Eureka)
3.1 Why Eureka?
❌
Without registry:
❌
Services need hardcoded URLs
❌
Hard to scale dynamically
Failure handling becomes tough
With Eureka:
✔ Services register dynamically
✔ Clients resolve service names
✔ Built-in load balancing
✔ Failover support
✔ Heartbeat mechanism
3.2 Eureka Server Setup
Step 1: Add dependency
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Step 2: Enable Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication { }
Step 3: [Link]
[Link]=8761
[Link]=eureka-server
[Link]-with-eureka=false
[Link]-registry=false
3.3 Registering a Service with Eureka (Client)
Add dependency:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Enable client:
@SpringBootApplication
@EnableEurekaClient
public class ProductService { }
Properties:
[Link]=product-service
[Link]=[Link]
Now Product Service → automatically visible in Dashboard.
4. API Gateway (Spring Cloud Gateway)
4.1 Why API Gateway?
API Gateway:
✔ Single entry point
✔ Routes traffic to services
✔ Central authentication / JWT
✔ Rate limiting
✔ Logging & metrics
✔ Response caching
✔ Hides internal network
4.2 Gateway Setup
Dependency:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
Routing config:
spring:
cloud:
gateway:
routes:
- id: product-service
uri: lb://product-service
predicates:
- Path=/products/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/orders/**
lb:// → Load-balanced URI via Eureka.
4.3 Custom Filters Example
@Component
public class LoggingFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange ex,
GatewayFilterChain chain) {
[Link]("Request path = " +
[Link]().getURI());
return [Link](ex);
}
public int getOrder() { return -1; }
}
5. Inter-Service Communication
5.1 Method 1: Feign Client
Add dependency:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Enable Feign:
@EnableFeignClients
@SpringBootApplication
public class OrderService { }
Feign Client:
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/products/{id}")
ProductResponse getProduct(@PathVariable Long id);
}
Use it:
@Autowired ProductClient client;
public OrderResponse placeOrder(Long id) {
ProductResponse product = [Link](id);
return new OrderResponse("SUCCESS", product);
}
5.2 Method 2: WebClient (Reactive)
@Autowired
private [Link] webClient;
public Mono<Product> getProduct(Long id) {
return [Link]().get()
.uri("[Link] + id)
.retrieve()
.bodyToMono([Link]);
}
5.3 Method 3: RestTemplate (Legacy)
@Autowired RestTemplate restTemplate;
@LoadBalanced
@Bean
public RestTemplate restTemplate() { return new RestTemplate(); }
public Product getProduct(Long id) {
return [Link](
"[Link] + id, [Link]);
}
Comparison Chart
Feature Feign WebClient RestTemplat
e
Declarative Yes No No
Async No Yes No
Recommende ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
d
Load Yes Yes Yes
Balanced
Code Cleanest Medium Verbose
6. Microservice Design Patterns (Top 5)
6.1 API Gateway Pattern
Use Gateway to centralize routing, authentication, rate limiting.
6.2 Circuit Breaker Pattern
Used when downstream service is slow or failing.
6.3 Retry & Timeout Pattern
Used to retry transient failures.
6.4 Saga Pattern
Distributed transactions using compensating actions.
6.5 Outbox Pattern
Ensures reliable event publishing from DB.
7. Circuit Breaker Pattern
7.1 Real-Time Use Case
Order-Service → Payment-Service
Scenario:
● Payment-Service is slow (3 seconds per request)
● Order-Service is overloaded due to waiting
● Users see timeouts → bad UX
Solution:
✔ Use Circuit Breaker
✔ Use Retry + Timeout
✔ Provide fallback response
✔ Avoid cascading failures
7.2 Dependencies
<dependency>
<groupId>[Link].resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
7.3 Configuration
[Link]:
instances:
paymentService:
slidingWindowSize: 10
minimumNumberOfCalls: 5
failureRateThreshold: 50
waitDurationInOpenState: 10s
[Link]:
instances:
paymentService:
maxAttempts: 3
waitDuration: 500ms
7.4 Circuit Breaker + Retry + Fallback
(Code)
@Service
public class PaymentClient {
@Autowired
private [Link] webClient;
@Retry(name = "paymentService")
@CircuitBreaker(name = "paymentService", fallbackMethod =
"fallback")
@TimeLimiter(name = "paymentService")
public CompletableFuture<PaymentResponse> makePayment(Long
orderId) {
return [Link]().post()
.uri("[Link]
.bodyValue(new PaymentRequest(orderId, 500.0))
.retrieve()
.bodyToMono([Link])
.toFuture();
}
public CompletableFuture<PaymentResponse> fallback(Long orderId,
Throwable ex) {
PaymentResponse resp =
new PaymentResponse("PENDING",
"Payment service unreachable. Will retry soon.");
return [Link](resp);
}
}
7.5 What Happens During Failure?
Stage Behavior
Normal All calls go to Payment Service
Failures exceed Circuit opens
threshold
Open state Immediately fails without calling Payment
Half-open Tries 1–2 requests
Recovery Circuit closed if service healthy
7.6 Interview Questions (Circuit Breaker)
Q1: Why do we use Circuit Breaker in microservices?
To prevent cascading failures when a downstream system is slow or down.
Q2: Difference between Retry and Circuit Breaker?
Retry handles temporary failures.
Circuit breaker stops calling a consistently failing service.
Q3: What is Half-Open state?
Phase where system tests if the failing service has recovered.
FINAL SUMMARY TABLE
Topic Must Know
Microservices Why/when/how
basics
Eureka Service registry
Gateway Routing + security
Feign Best for internal calls
WebClient Reactive calls
RestTemplate Legacy
Patterns CB, Retry, Saga,
Outbox
Circuit Breaker Real-time coding