Microservices Interview Preparation
A. Microservices Fundamentals Microservices is an architectural style where a large application is divided into multiple small, independent services. Each microservice: Performs one specific business functionHas its own…
A. Microservices Fundamentals
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Microservices is an architectural style where a large application is divided into multiple small, independent services.
Each microservice:
- Performs one specific business function
- Has its own codebase
- Can have its own database
- Can be developed independently
- Can be deployed independently
- Communicates with other services using APIs (REST, gRPC, Messaging, etc.)
Unlike a monolithic application, every service can be built, tested, scaled, and deployed separately.
Example
Consider an E-commerce application.
Instead of one large application, we split it into:
- User Service
- Product Service
- Order Service
- Payment Service
- Inventory Service
- Notification Service
Each service works independently.
If Payment Service crashes, User Service and Product Service can still continue working.
Real World Example
Amazon is one of the most famous examples of Microservices.
Instead of one huge application, Amazon has thousands of independent services handling:
- Orders
- Payments
- Recommendations
- Search
- Reviews
- Shipping
- Notifications
Each team owns its own service.
Interview Tip: Microservices are not simply small Spring Boot projects.
A true microservice:
- Owns a business capability
- Owns its data
- Can be independently deployed
- Is loosely coupled with other services
Follow-up Questions
- Why are Microservices called loosely coupled?
- Can one microservice have multiple databases?
- Can two services directly access each other’s database?
A Monolithic Architecture is a single application where all modules are packaged and deployed together.
A Microservices Architecture breaks the application into multiple independent services.
Example
Monolith:
E-Commerce Application
-------------------------
User Module
Product Module
Order Module
Payment Module
Inventory Module
-------------------------
One Deployment
One Codebase
Usually One Database
Microservices:
User Service
Product Service
Order Service
Payment Service
Inventory Service
Each deployed independently
Comparison
| Feature | Monolith | Microservices |
|———-|———–|————–|
| Deployment | Single | Independent |
| Codebase | One | Multiple |
| Database | Usually Shared | Database per Service |
| Scalability | Entire application | Individual service |
| Fault Isolation | Low | High |
| Development | Easier initially | More complex |
| Team Size | Small | Large organizations |
| Technology Stack | Usually same | Can differ per service |
Example
If Payment Service experiences heavy traffic:
Monolith
Entire application must be scaled.
Microservices
Only Payment Service needs scaling.
This saves infrastructure cost.
Interview Tip: Interviewers often ask:
Why does Netflix use Microservices while small startups still use Monolith?
Answer:
Because Microservices introduce operational complexity.
Small projects usually don’t need that complexity.
Follow-up Questions
- Which architecture would you choose for a startup?
- Can a Monolith become Microservices later?
- Which is easier to debug?
Microservices evolved to overcome the limitations of Monolithic Architecture.
As applications became larger, maintaining a single codebase became difficult.
Some common problems with Monolith were:
- Long deployment times
- Difficult scaling
- Tight coupling
- Large development teams causing merge conflicts
- Single point of failure
- Slow release cycles
Microservices solve these problems by dividing the application into independent services.
Example
Suppose Payment Module changes frequently.
In a Monolith:
- Entire application must be rebuilt.
- Entire application must be tested.
- Entire application must be deployed.
In Microservices:
Only Payment Service is redeployed.
Real World Example
Companies like:
- Netflix
- Amazon
- Uber
- Spotify
moved to Microservices because their monolithic systems became too large to manage efficiently.
Interview Tip: Microservices were introduced to solve organizational and scalability problems, not because Monolith is a bad architecture.
Follow-up Questions
- What problems do Microservices solve?
- Are Microservices always faster?
- Can every application benefit from Microservices?
Some major advantages are:
1. Independent Deployment
Each service can be deployed without affecting other services.
2. Independent Scaling
Only heavily used services need additional resources.
Example:
Payment Service receives high traffic during sales.
Only Payment Service is scaled.
3. Fault Isolation
Failure in one service doesn’t necessarily bring down the entire application.
4. Technology Flexibility
Different services can use different technologies.
Example:
- Java for Order Service
- Node.js for Notification
- Python for AI Recommendation
5. Faster Development
Different teams can work on different services simultaneously.
6. Easier Maintenance
Small codebases are easier to understand and maintain.
7. Better CI/CD
Independent deployment enables faster releases.
Interview Tip: Do not simply memorize advantages.
Explain why they matter in production.
Example:
“Independent deployment reduces release risk because deploying one service doesn’t require redeploying the entire application.”
Follow-up Questions
- Which advantage is most useful in production?
- How do Microservices improve DevOps?
Although Microservices offer many benefits, they also introduce significant complexity.
1. Distributed System Complexity
Managing communication between services is difficult.
2. Network Latency
Unlike method calls inside a Monolith, Microservices communicate over the network, increasing response time.
3. Data Consistency
Maintaining consistency across multiple databases is challenging.
Often solved using Saga Pattern or Event-Driven Architecture.
4. Deployment Complexity
Managing many services requires CI/CD pipelines, Docker, Kubernetes, and monitoring tools.
5. Monitoring & Logging
Logs are distributed across multiple services.
Centralized logging is usually required.
6. Testing Complexity
Integration testing becomes more challenging because multiple services interact.
7. Increased Infrastructure Cost
Running many services often requires more servers, containers, and orchestration tools.
Interview Tip: Microservices solve some problems while introducing others.
They’re a trade-off, not a universal solution.
Follow-up Questions
- What is the biggest challenge in Microservices?
- Why is distributed debugging difficult?
Microservices are not suitable for every application.
Avoid Microservices when:
- The application is small.
- A single team is building the product.
- Business requirements are simple.
- Deployment frequency is low.
- Team lacks DevOps expertise.
- The project is in the MVP or prototype stage.
In these situations, a Monolith is often a better choice because it is simpler to develop, test, and maintain.
Example
A small college management system with:
- Student Management
- Attendance
- Fee Management
can be effectively implemented as a Monolith.
Splitting it into multiple services would add unnecessary complexity.
Interview Tip: Many companies start with a Monolith and migrate to Microservices only when the application grows.
Follow-up Questions
- Can Microservices slow development?
- Why do startups prefer Monolith initially?
Well-designed Microservices generally have the following characteristics:
- Single Responsibility (one business capability)
- Loosely Coupled
- Highly Cohesive
- Independently Deployable
- Independently Scalable
- Database per Service
- Decentralized Governance
- Fault Isolation
- API-Based Communication
- Automation-Friendly (CI/CD)
Example
Order Service should only handle:
- Create Order
- Update Order
- Cancel Order
It should not directly manage payments or inventory.
Interview Tip: Remember the phrase:
u003e “High Cohesion, Low Coupling.”
It is one of the most important design goals in Microservices.
A good Microservices architecture follows these principles:
Single Responsibility
Each service should have one business responsibility.
Loose Coupling
Services should depend as little as possible on each other.
High Cohesion
Related functionality should stay within the same service.
Independent Deployment
Each service can be released independently.
Database per Service
Each service owns its own data.
Failure Isolation
A failure in one service should not bring down others.
API First
Services communicate through well-defined APIs.
Automation
Deployment, testing, monitoring, and scaling should be automated.
Interview Tip: Interviewers often ask:
What are the two most important principles?
A common answer is:
- Loose Coupling
- High Cohesion
The Database per Service pattern means that each microservice owns and manages its own database.
No other service should directly read from or write to that database.
Services communicate through APIs or asynchronous events instead of database access.
Example
User Service
│
User Database
Order Service
│
Order Database
Payment Service
│
Payment Database
If the Order Service needs user details, it should call the User Service API rather than querying the User database directly.
Advantages
- Strong service ownership
- Better encapsulation
- Independent schema evolution
- Independent scaling
- Reduced coupling
Interview Tip: Direct database sharing between services creates tight coupling and should be avoided.
A Shared Database allows multiple services to access the same database.
A Separate Database gives each service its own database.
| Feature | Shared Database | Separate Database |
|———|—————-|——————|
| Coupling | High | Low |
| Independent Deployment | Difficult | Easy |
| Data Ownership | Unclear | Clear |
| Scalability | Limited | Better |
| Technology Choice | Restricted | Flexible |
Interview Tip: Although a shared database may seem simpler initially, it often becomes a bottleneck as the system grows.
A service boundary defines the scope and responsibility of a microservice.
Each service should own a complete business capability rather than a technical layer.
Good Examples
- User Service
- Payment Service
- Inventory Service
- Order Service
Poor Examples
- Database Service
- Utility Service
- Validation Service
These are technical responsibilities, not business capabilities.
Interview Tip: A common mistake is creating services based on entities instead of business capabilities. Focus on what the business does, not just the database tables.
Domain-Driven Design (DDD) is an approach to designing software based on the business domain.
Instead of organizing code around technical layers, DDD focuses on business concepts and rules.
DDD helps identify the right service boundaries for Microservices.
Core Concepts
- Domain
- Subdomain
- Entity
- Value Object
- Aggregate
- Repository
- Domain Service
- Bounded Context
Example
In an e-commerce application:
- Ordering
- Payments
- Inventory
- Shipping
can each be modeled as separate domains or subdomains with their own business rules.
Interview Tip: DDD is not mandatory for Microservices, but it is one of the most effective approaches for designing scalable and maintainable service boundaries.
A Bounded Context is a central concept in Domain-Driven Design (DDD). It defines a clear boundary within which a particular domain model and business terminology are valid.
Inside a bounded context:
- Business rules are consistent.
- Data models are specific to that context.
- Teams can evolve the model independently without affecting other contexts.
Example
The term Customer may have different meanings:
Ordering Context
- Shipping address
- Delivery preferences
Billing Context
- Payment details
- Credit information
Although both use the term “Customer,” each context manages only the data relevant to its business responsibility.
Why is it Important?
Bounded Contexts help:
- Prevent tightly coupled services
- Reduce ambiguity in business models
- Enable independent development and deployment
- Align services with real business capabilities
Interview Tip: A well-defined bounded context often maps naturally to a single microservice, but one bounded context does not always have to be exactly one microservice.
Most Frequently Asked Questions from This Section (★★★★★)
1. What are Microservices?
2. Monolith vs Microservices.
3. Why did Microservices evolve?
4. Advantages and disadvantages of Microservices.
5. When should you choose Monolith over Microservices?
6. What is the Database per Service pattern?
7. Shared Database vs Separate Database.
8. What are service boundaries?
9. What is Domain-Driven Design (DDD)?
10. What is a Bounded Context, and why is it important in Microservices?
This structure is well-suited for an interview-preparation website because it mirrors real interview conversations, includes practical production examples, and highlights common follow-up questions that interviewers frequently ask.
B. Inter-Service Communication
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Synchronous communication is a communication style where one microservice sends a request to another microservice and waits for the response before continuing its execution.
The caller service remains blocked until it receives a response or a timeout occurs.
It is commonly implemented using HTTP-based REST APIs, OpenFeign, WebClient, or gRPC.
For example, when the Order Service needs customer details, it sends a request to the User Service and waits for the response before creating the order.
Synchronous communication is suitable when an immediate response is required.
REST (Representational State Transfer) is the most commonly used communication mechanism between microservices.
In REST communication, one service exposes REST APIs, and other services consume those APIs over HTTP using methods like GET, POST, PUT, and DELETE.
REST typically exchanges data in JSON format.
For example, when an Order Service needs product details, it calls the Product Service’s REST API.
REST is simple, language-independent, easy to debug, and widely supported, making it the preferred choice for most enterprise applications.
OpenFeign is a declarative HTTP client provided by Spring Cloud that simplifies communication between microservices.
Instead of writing HTTP request logic manually, developers define a Java interface, and OpenFeign automatically generates the implementation.
This reduces boilerplate code and improves readability.
OpenFeign also integrates well with Spring Cloud LoadBalancer, Eureka, and Circuit Breakers like Resilience4j.
It is commonly used for synchronous REST communication between internal microservices.
WebClient is the modern non-blocking HTTP client introduced in Spring WebFlux.
Unlike RestTemplate, WebClient supports asynchronous and reactive programming.
It can handle a large number of concurrent requests without blocking threads, making it more efficient for high-performance applications.
WebClient can also be used synchronously by calling .block(), although its primary purpose is non-blocking communication.
It is recommended over RestTemplate because RestTemplate is in maintenance mode.
gRPC is a high-performance Remote Procedure Call (RPC) framework developed by Google.
Unlike REST, which typically uses HTTP and JSON, gRPC uses HTTP/2 and Protocol Buffers (Protobuf) for communication.
Because Protocol Buffers are compact and binary, gRPC provides lower latency and faster communication than REST.
It is commonly used for internal communication between microservices where performance is critical.
OpenFeign is a declarative HTTP client designed for simple synchronous REST communication.
WebClient is a reactive HTTP client designed for asynchronous and non-blocking communication.
OpenFeign is easier to use and requires less code, making it ideal for standard CRUD operations between services.
WebClient provides better scalability under high concurrency because it does not block threads while waiting for responses.
Choose OpenFeign for simple service-to-service communication and WebClient for reactive or high-throughput applications.
REST communicates using HTTP and usually exchanges data in JSON format.
gRPC uses HTTP/2 and Protocol Buffers, resulting in faster serialization, smaller payloads, and lower latency.
REST is human-readable, browser-friendly, and widely adopted for public APIs.
gRPC is better suited for internal microservice communication where high performance and low latency are important.
REST is generally easier to test and debug, whereas gRPC offers better efficiency.
Asynchronous communication is a communication style where one service sends a message and continues processing without waiting for an immediate response.
The receiving service processes the message independently.
This is commonly implemented using message brokers such as Kafka or RabbitMQ.
For example, after an order is placed, the Order Service publishes an event, and Notification Service, Inventory Service, and Payment Service process that event independently.
Asynchronous communication improves scalability, fault tolerance, and loose coupling.
Event-Driven Architecture (EDA) is an architectural pattern where services communicate by publishing and consuming events instead of calling each other directly.
When a business event occurs, such as “Order Created,” the producing service publishes an event to a message broker.
Interested services subscribe to that event and perform their own processing independently.
This approach reduces coupling and allows services to evolve independently.
Event-Driven Architecture is widely used in modern microservices.
Apache Kafka is a distributed event streaming platform used for high-throughput, fault-tolerant asynchronous communication.
Kafka stores messages in topics and allows multiple producers and consumers to exchange data reliably.
Unlike traditional messaging systems, Kafka retains messages for a configurable period, allowing consumers to replay them if required.
Kafka is commonly used for event-driven architectures, log processing, analytics, and real-time data pipelines.
RabbitMQ is a message broker that enables asynchronous communication between applications.
It receives messages from producers and delivers them to consumers using queues.
RabbitMQ is known for reliable message delivery, flexible routing, acknowledgements, retries, and message durability.
It is widely used for task queues, background processing, email notifications, and business workflows.
Unlike Kafka, RabbitMQ focuses more on message delivery than long-term event storage.
A Producer is the application or microservice that creates and sends messages to a message broker.
The producer does not know which service will consume the message.
Its responsibility is only to publish the event.
For example, after successfully creating an order, the Order Service publishes an “Order Created” event to Kafka.
A Consumer is the application or microservice that receives and processes messages from a message broker.
Consumers subscribe to specific topics or queues and perform business operations based on received events.
For example, after receiving an “Order Created” event, the Inventory Service reserves stock while the Notification Service sends a confirmation email.
A Topic is a logical channel in Kafka where producers publish messages and consumers read those messages.
Topics organize related events together.
For example, an e-commerce application may have separate topics such as:
- order-created
- payment-success
- inventory-updated
Consumers subscribe only to the topics they are interested in.
A Partition is a subdivision of a Kafka topic.
Messages within a partition are stored in order.
Having multiple partitions allows Kafka to process messages in parallel, increasing throughput and scalability.
Each partition can be assigned to different consumers within a consumer group.
More partitions generally improve parallel processing but also increase management complexity.
A Consumer Group is a group of consumers that work together to process messages from a Kafka topic.
Within a consumer group, each partition is consumed by only one consumer at a time.
This enables parallel processing while ensuring that the same message is not processed multiple times within the group.
Different consumer groups can independently consume the same topic.
An Offset is a unique sequential number assigned to every message within a Kafka partition.
Kafka uses offsets to track which messages have already been processed by each consumer.
Consumers commit offsets after successfully processing messages.
If a consumer fails before committing an offset, Kafka can deliver the message again, preventing data loss.
Offsets also allow consumers to replay previously processed events when needed.
A Dead Letter Queue (DLQ) is a special queue or topic used to store messages that cannot be processed successfully after multiple retry attempts.
Instead of repeatedly failing the same message, the system moves it to the DLQ for later investigation.
This prevents problematic messages from blocking normal message processing.
DLQs improve system reliability and simplify debugging.
Message ordering refers to processing messages in the same sequence in which they were produced.
In Kafka, ordering is guaranteed only within a single partition.
If messages related to the same business entity must remain ordered, they should be published using the same partition key.
Across multiple partitions, Kafka does not guarantee global ordering.
Eventual Consistency is a consistency model in which different services may temporarily have different data, but they eventually become consistent after all events are processed.
Unlike traditional distributed transactions, microservices often rely on asynchronous events to synchronize data.
For example, after placing an order, the Inventory Service, Payment Service, and Notification Service may update their data at different times, but eventually all services reflect the correct business state.
Eventual consistency improves scalability and availability in distributed systems.
C. Service Discovery & Load Balancing
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
In a microservices architecture, service instances are created, terminated, and scaled dynamically. Their IP addresses and ports frequently change.
Service Discovery provides a mechanism for services to locate each other without hardcoding network addresses.
Instead of remembering IP addresses, a service simply requests another service by its logical name, and the service registry provides the current available instance.
This improves scalability, flexibility, and fault tolerance.
Eureka Server is a service registry provided by Spring Cloud Netflix.
It maintains a registry of all available microservice instances running in the system.
Whenever a microservice starts, it registers itself with the Eureka Server.
Other services query Eureka to discover available instances instead of using fixed IP addresses.
This enables dynamic service discovery in distributed systems.
A Eureka Client is a microservice that registers itself with the Eureka Server and can also discover other registered services.
When the application starts, it sends its service name, IP address, port, and health information to Eureka.
The client periodically sends heartbeats to indicate that it is still running.
Whenever it needs another service, it queries Eureka instead of using a hardcoded endpoint.
Service Registration is the process in which a microservice registers itself with the service registry during startup.
The registry stores information such as:
- Service name
- IP address
- Port number
- Health status
Once registered, other services can discover and communicate with it dynamically.
When a microservice starts, it first registers itself with the Eureka Server.
The Eureka Server stores its information in the registry.
When another service needs to communicate with it, it requests the service information from Eureka using the service name.
Eureka returns one or more available instances.
The client-side load balancer selects one instance and sends the request.
This entire process happens automatically without hardcoded URLs.
Heartbeats are periodic messages sent by Eureka Clients to the Eureka Server.
They inform the server that the service instance is healthy and still running.
If the server does not receive heartbeats for a configured duration, it marks the instance as unavailable and eventually removes it from the registry.
Heartbeats ensure that only healthy services receive traffic.
Self-Preservation Mode is a safety mechanism in Eureka Server designed to prevent accidental removal of healthy services during network failures.
If Eureka notices that a large number of heartbeats are suddenly missing, it assumes there may be a network issue rather than actual service failures.
Instead of removing those instances, Eureka temporarily keeps them in the registry until normal heartbeats resume.
This helps avoid unnecessary service outages caused by transient network problems.
In Client-Side Load Balancing, the client itself is responsible for selecting which service instance should receive the request.
The client first retrieves the list of available instances from the service registry.
It then applies a load-balancing algorithm, such as Round Robin, to choose one instance.
This reduces dependency on an external load balancer and distributes traffic efficiently across service instances.
Spring Cloud LoadBalancer is the default client-side load-balancing library in Spring Cloud.
It replaces the deprecated Netflix Ribbon.
It works with service discovery tools like Eureka to distribute requests across multiple service instances.
When multiple instances of a service are available, Spring Cloud LoadBalancer automatically selects one instance using a configurable load-balancing strategy.
Ribbon was the original client-side load balancer provided by Netflix and integrated with Spring Cloud.
As Netflix stopped actively developing many of its OSS components, including Ribbon, Spring Cloud deprecated Ribbon and introduced Spring Cloud LoadBalancer as its replacement.
Spring Cloud LoadBalancer is actively maintained, better integrated with the Spring ecosystem, and is the recommended choice for modern Spring Boot microservices.
D. API Gateway
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
In a microservices architecture, clients would otherwise have to communicate directly with multiple services, making the system complex and difficult to manage.
An API Gateway acts as a single entry point for all client requests. It receives incoming requests, performs cross-cutting tasks such as authentication, authorization, logging, rate limiting, and routing, and then forwards the request to the appropriate microservice.
Using an API Gateway simplifies client communication, improves security, and centralizes common functionalities.
Spring Cloud Gateway is the official API Gateway provided by Spring Cloud for routing requests to microservices.
It is built on Spring WebFlux and supports non-blocking, reactive request processing.
Spring Cloud Gateway provides features such as request routing, authentication, authorization, rate limiting, filtering, CORS handling, request and response transformation, and load balancing.
It is the recommended API Gateway solution for modern Spring Boot microservices.
Request Routing is the process of forwarding an incoming client request to the appropriate microservice based on predefined routing rules.
The API Gateway examines the request, such as its URL, HTTP method, or headers, and determines which service should handle it.
For example:
- /users/** → User Service
- /orders/** → Order Service
- /payments/** → Payment Service
This allows clients to communicate with a single endpoint while the gateway handles service routing internally.
Gateway Filters are components that intercept requests before they reach a microservice and responses before they are returned to the client.
They are used to perform common operations such as:
- Authentication
- Authorization
- Logging
- Request validation
- Rate limiting
- Header modification
- Request and response transformation
Since filters are implemented at the gateway level, these functionalities do not need to be duplicated in every microservice.
Authentication at the API Gateway means verifying the identity of the client before forwarding the request to any microservice.
The gateway validates credentials such as JWT tokens, OAuth2 access tokens, or API keys.
If authentication succeeds, the request is forwarded to the target service.
If authentication fails, the gateway immediately rejects the request with an appropriate HTTP status code, preventing unauthorized users from accessing internal services.
Authorization is the process of determining whether an authenticated user has permission to access a specific resource.
After successful authentication, the API Gateway or the target microservice checks the user’s roles or permissions.
For example:
- An ADMIN can access /admin/**
- A CUSTOMER can access /orders/**
- A GUEST may only access public APIs
Authentication identifies the user, while authorization determines what the user is allowed to do.
API Aggregation is a pattern where the API Gateway combines responses from multiple microservices into a single response for the client.
Instead of the client making multiple API calls, it sends one request to the gateway.
The gateway internally calls multiple services, collects their responses, combines them, and returns a single consolidated response.
For example, a Product Details page may require information from:
- Product Service
- Inventory Service
- Review Service
- Pricing Service
The gateway aggregates all responses and returns one complete response to the client.
Rate Limiting is a mechanism that restricts the number of requests a client can make within a specified time period.
Its primary purpose is to protect applications from abuse, excessive traffic, brute-force attacks, and denial-of-service attacks.
For example, an API may allow a client to make only 100 requests per minute.
If the limit is exceeded, the API Gateway rejects additional requests until the limit resets, typically returning HTTP status code 429 (Too Many Requests).
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls whether a web application can make requests to a different domain, protocol, or port.
By default, browsers block cross-origin requests for security reasons.
The API Gateway can be configured to allow requests only from trusted origins by setting appropriate CORS headers.
This enables secure communication between frontend applications and backend microservices hosted on different domains.
Request Transformation is the process of modifying an incoming request before forwarding it to the target microservice.
The API Gateway can:
- Add or remove headers
- Modify request parameters
- Rewrite URLs
- Add authentication information
- Convert request formats
This allows backend services to remain simple while the gateway handles request customization.
Response Transformation is the process of modifying a response before sending it back to the client.
The API Gateway can:
- Remove sensitive information
- Add custom headers
- Change response structure
- Convert response formats
- Standardize error responses
This helps maintain consistent API responses without modifying individual microservices.
An API Gateway and a Load Balancer serve different purposes in a microservices architecture.
A Load Balancer distributes incoming requests across multiple instances of the same service to improve availability and scalability.
An API Gateway acts as the entry point for clients and provides additional features such as routing, authentication, authorization, rate limiting, filtering, request transformation, response transformation, and API aggregation.
While an API Gateway may internally use load balancing, a Load Balancer alone cannot replace the full functionality of an API Gateway.
Therefore, a Load Balancer focuses on traffic distribution, whereas an API Gateway focuses on API management and request processing.
E. Distributed Configuration
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
In a microservices architecture, multiple services require configuration such as database credentials, API URLs, feature flags, and environment-specific properties.
Managing these configurations separately for every service becomes difficult and error-prone.
Centralized Configuration stores all configuration files in one central location, allowing multiple services to retrieve their configuration from a single source.
This improves consistency, simplifies maintenance, and enables configuration changes without modifying every application individually.
Spring Cloud Config Server is a centralized configuration management server provided by Spring Cloud.
It stores configuration files in a central repository, typically Git, and serves them to multiple microservices at runtime.
Instead of maintaining separate configuration files for each application, all services retrieve their configuration from the Config Server during startup.
This provides centralized management, version control, and easier configuration updates.
A Config Client is a microservice that retrieves its configuration from the Spring Cloud Config Server during startup.
Instead of reading configuration only from its local `application.properties` or `application.yml` file, the client contacts the Config Server and downloads the required configuration.
This ensures that all services use consistent and centrally managed configuration.
Git-backed Configuration is a feature of Spring Cloud Config Server where configuration files are stored in a Git repository.
The Config Server reads configuration from Git and provides it to client applications.
Using Git offers several benefits:
- Version control
- Change history
- Rollback support
- Collaboration among teams
- Environment-specific configuration management
This approach allows configuration changes without rebuilding application code.
Dynamic Configuration Refresh allows a microservice to reload updated configuration values without restarting the application.
When configuration changes in the Config Server, the client application can refresh its configuration and immediately start using the new values.
This minimizes downtime and avoids unnecessary application restarts.
Dynamic refresh is commonly used for feature flags, API endpoints, and application settings.
@RefreshScope is a Spring Cloud annotation that allows Spring-managed beans to reload their configuration whenever a refresh operation is triggered.
Without @RefreshScope, updated configuration values are not reflected until the application restarts.
By annotating a bean with @RefreshScope, Spring recreates that bean using the latest configuration values after a refresh event.
It is commonly used for beans that depend on dynamic configuration.
Spring Cloud Bus is a messaging mechanism that distributes configuration refresh events across multiple microservice instances.
Instead of manually refreshing each service, Spring Cloud Bus broadcasts a refresh event through a message broker such as RabbitMQ or Kafka.
All subscribed services receive the event and automatically refresh their configuration.
This is especially useful in large distributed systems with many service instances.
Environment-specific Configuration allows different configuration values to be used for different deployment environments such as Development, Testing, Staging, and Production.
For example:
- Development may use a local database.
- Testing may use a QA database.
- Production uses the production database.
Spring Cloud Config Server organizes separate configuration files for each environment, ensuring that applications automatically load the correct configuration based on the active profile.
Secret Management is the secure storage and management of sensitive information such as passwords, API keys, access tokens, encryption keys, and database credentials.
Sensitive information should never be hardcoded in application code or configuration files.
Instead, secrets should be stored in secure secret management solutions such as HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or Kubernetes Secrets.
This improves security and simplifies secret rotation.
F. Fault Tolerance & Resilience
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
A Circuit Breaker is a fault tolerance pattern that prevents repeated calls to a failing service.
When a service continuously fails, the Circuit Breaker temporarily stops sending requests to that service and immediately returns an alternative response.
This prevents unnecessary load on the failing service and allows it time to recover.
Once the service becomes healthy again, the Circuit Breaker gradually allows requests to pass through.
In a distributed system, one failing service can negatively affect many other services.
Without a Circuit Breaker, applications continue sending requests to an unhealthy service, leading to increased response times, thread exhaustion, and system-wide failures.
A Circuit Breaker detects repeated failures and temporarily blocks further requests, protecting both the calling service and the failing service.
This improves system stability and availability.
Resilience4j is a lightweight fault tolerance library for Java applications.
It provides resilience patterns such as:
- Circuit Breaker
- Retry
- Rate Limiter
- Bulkhead
- Time Limiter
- Cache
Resilience4j integrates seamlessly with Spring Boot and is the recommended replacement for Netflix Hystrix, which is no longer actively maintained.
Retry is a resilience pattern in which a failed operation is attempted again before reporting failure to the user.
Temporary failures such as network issues or brief service unavailability often succeed on subsequent attempts.
Retry improves application reliability by automatically reattempting failed requests according to configured retry policies.
However, retries should be limited to avoid overloading already struggling services.
Retry with Exponential Backoff is a retry strategy where the delay between consecutive retry attempts increases exponentially.
For example, retries may occur after:
- 1 second
- 2 seconds
- 4 seconds
- 8 seconds
This approach prevents continuous immediate retries that could further overload a failing service.
Exponential backoff is commonly used in distributed systems to improve stability during temporary failures.
The Bulkhead Pattern isolates resources so that failures in one part of the application do not affect others.
It divides application resources, such as thread pools or connection pools, into separate compartments.
If one compartment becomes overloaded, the remaining compartments continue functioning normally.
This improves fault isolation and prevents a single failing service from exhausting shared resources.
A Rate Limiter controls how many requests are allowed within a specified time period.
Its purpose is to prevent excessive traffic from overwhelming a service.
If requests exceed the configured limit, additional requests are rejected or delayed until the next time window.
Rate limiting protects applications from abuse, brute-force attacks, and sudden traffic spikes.
A Time Limiter defines the maximum amount of time an application waits for an operation to complete.
If the operation exceeds the configured timeout, it is automatically cancelled or considered failed.
This prevents threads from remaining blocked indefinitely while waiting for slow services.
Time Limiters improve responsiveness and resource utilization in distributed applications.
Timeout Handling is the process of terminating a request when it exceeds a predefined waiting period.
Instead of waiting indefinitely for a slow or unresponsive service, the application stops waiting and returns an error or fallback response.
Proper timeout configuration prevents thread exhaustion, improves responsiveness, and enhances overall system stability.
A Fallback Method is an alternative method that executes when the primary service call fails.
Instead of returning an exception to the client, the fallback provides a predefined response, cached data, or a default value.
For example, if the Recommendation Service is unavailable, the application may return popular products instead of personalized recommendations.
Fallback methods improve user experience during service failures.
Fail Fast is a design principle where an application immediately reports failure when it detects an unrecoverable problem instead of continuing unnecessary processing.
This prevents wasted resources and allows clients to receive quick error responses.
Fail Fast is commonly used with validation failures, authentication failures, and service unavailability where retries are unlikely to succeed.
Graceful Degradation is the ability of an application to continue providing essential functionality even when some services are unavailable.
Instead of failing completely, the application disables only the affected features.
For example, if the Recommendation Service fails, users can still browse products and place orders, while recommendations are temporarily unavailable.
This improves system availability and user experience.
Retry and Circuit Breaker both improve fault tolerance but solve different problems.
Retry attempts a failed request again, assuming the failure is temporary.
A Circuit Breaker monitors repeated failures and temporarily stops sending requests to an unhealthy service.
Retry is useful for transient failures such as temporary network issues.
Circuit Breaker is useful when a service is consistently failing and needs time to recover.
In production systems, Retry and Circuit Breaker are often used together for maximum resilience.
Cascading Failure occurs when the failure of one microservice causes failures in other dependent services, eventually affecting the entire system.
For example, if the Payment Service becomes slow, the Order Service waits for responses, exhausting its thread pool.
As the Order Service slows down, additional services depending on it also begin to fail.
This chain reaction can lead to widespread system outages.
Fault tolerance mechanisms such as Circuit Breakers, Timeouts, Bulkheads, and Rate Limiters help prevent cascading failures by isolating failures before they spread.
G. Distributed Data Management
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
A Distributed Transaction is a transaction that spans multiple microservices or multiple databases.
Unlike a local transaction, where all operations occur within a single database, a distributed transaction involves multiple independent services, each managing its own database.
The main challenge is ensuring that either all operations succeed or appropriate compensating actions are performed when failures occur.
Distributed transactions are commonly managed using patterns such as Saga instead of traditional database transactions.
A Local Transaction works only within a single database.
In a microservices architecture, each service owns its own database, so a transaction cannot span multiple databases using the traditional ACID model.
For example, if the Order Service successfully creates an order but the Payment Service fails to process payment, the Order database cannot automatically roll back the Payment database.
This limitation makes local transactions unsuitable for distributed systems.
The Saga Pattern is a distributed transaction management pattern used in microservices.
Instead of executing one large transaction across multiple services, a Saga breaks the business process into a sequence of smaller local transactions.
Each service completes its own transaction and then triggers the next service.
If one step fails, compensating transactions are executed to undo the completed operations.
Saga helps maintain data consistency without using distributed database transactions.
Choreography Saga is an implementation of the Saga Pattern where services communicate through events without a central coordinator.
Each service listens for events, performs its local transaction, and publishes the next event.
For example:
- Order Service publishes Order Created
- Payment Service processes payment and publishes Payment Completed
- Inventory Service reserves stock and publishes Inventory Reserved
Each service independently decides what to do next based on received events.
This approach provides loose coupling but can become difficult to manage as the number of services increases.
Orchestration Saga is an implementation of the Saga Pattern where a central orchestrator coordinates the entire business workflow.
The orchestrator instructs each service to perform its task and decides what action to take next.
If any step fails, the orchestrator invokes compensating transactions to undo previously completed operations.
This approach simplifies workflow management and provides better visibility into the overall business process.
Two-Phase Commit (2PC) is a distributed transaction protocol that ensures all participating systems either commit or roll back together.
In the first phase, the coordinator asks all participants whether they are ready to commit.
In the second phase, if every participant agrees, the coordinator instructs all participants to commit; otherwise, all participants roll back.
Although 2PC guarantees strong consistency, it is slow, tightly coupled, and can become a bottleneck.
Because of these limitations, modern microservices usually prefer the Saga Pattern over Two-Phase Commit.
Eventual Consistency is a consistency model where different microservices may temporarily contain different data, but they eventually become consistent after all events have been processed.
Unlike distributed transactions, services update their own databases independently and synchronize data asynchronously.
Temporary inconsistencies are acceptable because all services eventually reach the same business state.
This approach improves scalability and availability in distributed systems.
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates write operations from read operations.
Commands modify data, while queries only retrieve data.
This separation allows each side to be optimized independently.
For example, an Order Service may use one database for processing orders and another optimized database for reporting and searching.
CQRS improves scalability, performance, and flexibility in complex applications.
Event Sourcing is a design pattern in which the application’s state is reconstructed from a sequence of events rather than storing only the latest state.
Instead of updating a record directly, every business change is stored as an immutable event.
For example, instead of storing only the current account balance, events such as Account Created, Money Deposited, and Money Withdrawn are stored.
The current state can be recreated by replaying these events.
Event Sourcing provides complete audit history and supports event replay.
The Transactional Outbox Pattern ensures that database updates and event publishing happen reliably without data inconsistency.
Instead of directly publishing an event after updating the database, the application stores the event in an Outbox table as part of the same local transaction.
A separate background process reads the Outbox table and publishes the events to the message broker.
This guarantees that no event is lost even if the application crashes after updating the database.
The Inbox Pattern helps consumers process messages reliably and avoid duplicate processing.
When a consumer receives a message, it first stores the message identifier in an Inbox table.
Before processing a new message, it checks whether that message has already been processed.
If the message already exists in the Inbox, it is ignored.
The Inbox Pattern is commonly used together with the Outbox Pattern to achieve reliable message processing.
Idempotency means that performing the same operation multiple times produces the same result as performing it once.
In distributed systems, messages may be delivered more than once.
An idempotent operation ensures that duplicate requests do not create duplicate business records.
For example, if the same payment request is received twice due to network retries, only one payment should be processed.
Idempotency is essential for reliable distributed systems.
Duplicate messages are common in distributed systems because message brokers may deliver the same message more than once.
Applications should never assume that every message is delivered exactly once.
Duplicate messages can be handled using techniques such as:
- Idempotent business operations
- Unique message identifiers
- Inbox Pattern
- Database uniqueness constraints
- Processed message tracking
These techniques ensure that repeated messages do not cause duplicate business operations.
H. Security in Microservices
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
JWT (JSON Web Token) is commonly used to securely transfer user identity and authorization information between microservices.
When a user successfully authenticates, the Authentication Service generates a JWT and returns it to the client.
The client includes the token in subsequent requests.
The API Gateway validates the token before forwarding the request to downstream services.
Depending on the architecture, downstream services may either trust the gateway or independently validate the JWT.
JWT enables stateless authentication and avoids repeated database lookups for every request.
OAuth2 is an authorization framework that allows users to grant limited access to applications without sharing their credentials.
A typical OAuth2 architecture consists of:
- Resource Owner (User)
- Client Application
- Authorization Server
- Resource Server
The Authorization Server authenticates the user and issues an access token.
The client includes this token while accessing protected APIs hosted by the Resource Server.
OAuth2 is widely used for secure API access in distributed systems.
The API Gateway is typically responsible for authenticating incoming requests before forwarding them to internal microservices.
It validates credentials such as JWT tokens, OAuth2 access tokens, or API keys.
If authentication succeeds, the request is routed to the appropriate service.
If authentication fails, the gateway rejects the request immediately.
Centralizing authentication at the gateway simplifies security and avoids duplicate authentication logic across services.
Service-to-Service Authentication is the process of verifying the identity of one microservice before allowing it to communicate with another microservice.
Even though services communicate internally, they should not automatically trust each other.
Authentication can be implemented using JWT tokens, OAuth2 client credentials, mutual TLS (mTLS), or service identities.
This ensures that only authorized services can access protected internal APIs.
Token Propagation is the process of forwarding the authenticated user’s access token from one microservice to another.
For example, when the API Gateway forwards a request to the Order Service, the same JWT token can be propagated to the Payment Service.
This allows downstream services to identify the original user and perform authorization based on the user’s roles and permissions.
Token propagation helps maintain a consistent security context across multiple services.
RBAC (Role-Based Access Control) is an authorization mechanism where access to resources is granted based on user roles.
After authentication, the user’s roles are included in the security token.
Each microservice verifies whether the user has the required role before allowing access to protected resources.
For example:
- ADMIN can manage users.
- CUSTOMER can place orders.
- SUPPORT can view customer details.
RBAC ensures consistent authorization across distributed services.
Inter-service communication should be secured to prevent unauthorized access and data interception.
Common security practices include:
- Service-to-Service Authentication
- JWT or OAuth2 tokens
- Mutual TLS (mTLS)
- HTTPS encryption
- Network policies
- API Gateway validation
- Principle of least privilege
These mechanisms ensure that only trusted services can communicate with each other securely.
mTLS (Mutual Transport Layer Security) is an extension of TLS where both the client and the server authenticate each other using digital certificates.
In standard HTTPS, only the server presents its certificate.
In mTLS, both communicating services exchange and verify certificates before establishing a secure connection.
This provides strong authentication, encrypted communication, and protection against unauthorized service access.
mTLS is widely used to secure communication between internal microservices in production environments.
I. Observability
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Centralized Logging is the practice of collecting logs from multiple microservices into a single centralized logging system.
Instead of checking log files on individual servers, developers can search, analyze, and troubleshoot logs from one place.
Centralized logging simplifies debugging, monitoring, and root cause analysis in distributed systems.
Common tools used for centralized logging include the ELK Stack (Elasticsearch, Logstash, Kibana) and Grafana Loki.
Distributed Tracing is a technique used to track the complete journey of a request as it travels through multiple microservices.
Each service records timing and execution information for its part of the request.
This helps developers identify where delays or failures occur within a distributed system.
Distributed tracing is essential for debugging performance issues in microservices because a single user request often passes through several services.
A Correlation ID is a unique identifier assigned to a request when it enters the system.
The same Correlation ID is included in all logs generated by every microservice involved in processing that request.
This allows developers to collect and analyze all related logs for a particular request, making troubleshooting much easier.
Correlation IDs are especially useful in centralized logging systems.
A Trace ID is a unique identifier used in distributed tracing to track a request across multiple microservices.
Every request receives a Trace ID, and all spans generated while processing that request share the same Trace ID.
This allows tracing tools to reconstruct the complete execution path of a request from the entry point to the final response.
Trace IDs are commonly generated automatically by tracing frameworks such as OpenTelemetry.
OpenTelemetry is an open-source observability framework used to collect telemetry data from applications.
It provides APIs, libraries, and agents for generating:
- Traces
- Metrics
- Logs
OpenTelemetry exports this telemetry data to monitoring and tracing systems such as Jaeger, Zipkin, Prometheus, and Grafana.
It has become the industry standard for implementing observability in modern distributed systems.
Zipkin and Jaeger are distributed tracing systems used to visualize and analyze request flows across multiple microservices.
They collect trace information generated by applications and display:
- Request flow
- Service dependencies
- Request duration
- Latency
- Performance bottlenecks
These tools help developers identify slow or failing services in complex distributed environments.
Prometheus is an open-source monitoring and metrics collection system.
It periodically collects application and infrastructure metrics such as:
- CPU usage
- Memory usage
- Request count
- Response time
- Error rate
Prometheus stores these metrics in a time-series database and supports powerful querying for monitoring and alerting.
It is widely used for monitoring Spring Boot microservices.
Grafana is an open-source visualization and dashboard platform.
It connects to data sources such as Prometheus, Elasticsearch, Loki, and InfluxDB to display metrics through interactive dashboards.
Grafana enables teams to monitor application health, infrastructure performance, and business metrics in real time.
It also supports alerting based on configurable thresholds.
Log Aggregation is the process of collecting logs from multiple applications and storing them in a centralized logging platform for searching and analysis.
Two commonly used solutions are:
- ELK Stack – Elasticsearch stores logs, Logstash collects and processes logs, and Kibana provides visualization and search capabilities.
- Grafana Loki – A lightweight log aggregation system designed to work closely with Grafana for log storage and visualization.
Log aggregation allows developers to quickly locate errors, analyze system behavior, and troubleshoot production issues.
Monitoring Microservices involves continuously tracking the health, performance, and availability of all services in a distributed system.
A complete monitoring solution typically includes:
- Metrics collection using Prometheus
- Dashboard visualization using Grafana
- Centralized logging using ELK Stack or Loki
- Distributed tracing using OpenTelemetry with Jaeger or Zipkin
- Health checks using Spring Boot Actuator
- Alerting for failures, high latency, and resource usage
By combining logs, metrics, and traces, teams can quickly detect issues, diagnose root causes, and maintain reliable production systems.
J. Deployment & Scalability
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Dockerizing a Microservice means packaging the application along with all its dependencies into a Docker image.
This image can run consistently across different environments without worrying about operating system differences or missing dependencies.
Each microservice is typically packaged into its own Docker image and runs inside its own Docker container.
Docker ensures that the application behaves the same in development, testing, and production environments.
Docker Compose is a tool used to define and run multiple Docker containers together using a single YAML configuration file.
In a microservices application, Docker Compose can start multiple services such as:
- API Gateway
- User Service
- Order Service
- MySQL
- Redis
- Kafka
With a single command, all containers are created, connected through a common network, and started together.
Docker Compose is mainly used for local development and testing.
Kubernetes is an open-source container orchestration platform used to deploy, manage, scale, and monitor containerized applications.
Instead of manually managing Docker containers, Kubernetes automatically handles:
- Deployment
- Scaling
- Load balancing
- Self-healing
- Service discovery
- Rolling updates
It has become the standard platform for running microservices in production.
A Pod is the smallest deployable unit in Kubernetes.
A Pod contains one or more containers that share the same network and storage resources.
In most Spring Boot applications, one Pod usually contains one microservice container.
If a Pod fails, Kubernetes automatically creates a new Pod to replace it.
Pods are temporary and should not be considered permanent.
A Deployment is a Kubernetes resource used to manage Pods.
It defines:
- Which container image should run
- Number of Pod replicas
- Update strategy
- Rollback configuration
When a Pod fails, the Deployment automatically creates a replacement Pod.
Deployments also support rolling updates and automatic rollbacks.
A Service is a Kubernetes resource that provides a stable network endpoint for a group of Pods.
Since Pods can be created and destroyed dynamically, their IP addresses frequently change.
A Service provides a fixed IP address and DNS name, allowing applications to communicate without worrying about changing Pod addresses.
Services also perform load balancing across multiple Pod instances.
A ConfigMap is a Kubernetes resource used to store non-sensitive configuration data separately from application code.
Examples include:
- Database URLs
- API endpoints
- Application properties
- Environment variables
ConfigMaps allow configuration changes without rebuilding Docker images.
This makes deployments more flexible and easier to manage.
Secrets are Kubernetes resources used to securely store sensitive information such as:
- Database passwords
- API keys
- JWT secrets
- Certificates
- Access tokens
Unlike ConfigMaps, Secrets are intended for confidential data.
Applications access Secrets as environment variables or mounted files.
Keeping sensitive information outside application code improves security.
A Rolling Update is a deployment strategy in Kubernetes that updates application instances gradually instead of replacing all instances at once.
New Pods are created while old Pods are terminated one by one.
This ensures that the application remains available during deployment with little or no downtime.
If the new version becomes healthy, the rollout continues until all Pods are updated.
Rollback is the process of reverting an application to its previous stable version if a deployment introduces errors or failures.
Kubernetes maintains deployment history, allowing administrators to quickly restore the last working version.
Rollback minimizes downtime and enables rapid recovery from faulty deployments.
Horizontal Scaling is the process of increasing application capacity by adding more instances of a microservice instead of increasing the resources of a single instance.
For example, increasing the number of Order Service Pods from 3 to 10 is horizontal scaling.
A load balancer distributes incoming requests among all running instances.
Horizontal scaling improves application availability, fault tolerance, and throughput.
A Stateless Service does not store client session or user-specific data within its own memory between requests.
Every request contains all the information needed for processing.
Because no state is stored locally, any instance of the service can process any request.
Stateless services are easier to scale horizontally and are the preferred design for microservices.
A Readiness Probe determines whether a container is ready to receive incoming requests.
When a Pod starts, Kubernetes checks the readiness probe.
If the readiness check succeeds, the Pod is added to the Service and begins receiving traffic.
If it fails, Kubernetes keeps the Pod running but does not send client requests to it until it becomes ready.
This prevents requests from reaching applications that are still initializing.
A Liveness Probe checks whether a running application is still healthy.
If the liveness probe fails repeatedly, Kubernetes assumes the application is stuck or unhealthy and automatically restarts the container.
Liveness probes help recover applications from deadlocks, infinite loops, or unresponsive states without manual intervention.
Graceful Shutdown is the process of stopping an application without interrupting requests that are currently being processed.
When the application receives a shutdown signal, it stops accepting new requests while allowing existing requests to complete.
After all active requests finish or a configured timeout is reached, the application shuts down safely.
Graceful shutdown prevents request failures, incomplete transactions, and data inconsistencies during deployments or server restarts.
K. Performance & Scalability
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Scaling is the process of increasing an application’s capacity to handle more traffic.
Vertical Scaling means increasing the resources of a single server, such as adding more CPU, RAM, or storage.
Horizontal Scaling means adding more instances of the application and distributing requests among them using a load balancer.
Vertical scaling has hardware limitations, whereas horizontal scaling provides better scalability, fault tolerance, and high availability.
Microservices generally prefer horizontal scaling because individual services can be scaled independently based on demand.
Redis is an in-memory data store commonly used as a cache in microservices.
Instead of repeatedly fetching frequently accessed data from the database, the application first checks Redis.
If the data exists in Redis, it is returned immediately. Otherwise, the application retrieves the data from the database, stores it in Redis, and returns it to the client.
Caching with Redis reduces database load, improves response time, and increases application performance.
Cache Invalidation is the process of removing or updating stale data stored in the cache.
If the underlying database data changes but the cache is not updated, users may receive outdated information.
Common cache invalidation strategies include:
- Time-To-Live (TTL)
- Cache Eviction
- Cache Update after Database Update
- Event-Based Cache Invalidation
Proper cache invalidation ensures that cached data remains accurate and consistent.
Connection Pooling is a technique where database connections are created once and reused instead of creating a new connection for every request.
Creating database connections is an expensive operation.
A connection pool maintains a set of reusable connections, allowing applications to borrow and return connections as needed.
This improves performance, reduces latency, and minimizes database overhead.
Spring Boot commonly uses HikariCP as the default connection pool implementation.
Thread Pool Tuning is the process of configuring the number of worker threads to achieve optimal application performance.
A thread pool that is too small may increase request waiting time.
A thread pool that is too large may consume excessive memory and CPU resources.
Proper tuning depends on factors such as CPU cores, request type, workload, and application architecture.
Well-configured thread pools improve throughput and resource utilization.
Async Processing allows long-running tasks to execute in the background without blocking the main application thread.
Instead of making the client wait for task completion, the application starts the task asynchronously and immediately returns control to the user.
Typical use cases include:
- Sending emails
- Generating reports
- Processing images
- Sending notifications
Async processing improves responsiveness and overall application performance.
API Compression reduces the size of HTTP request and response payloads before they are transmitted over the network.
Compression techniques such as GZIP or Brotli significantly reduce bandwidth usage and improve response time, especially for large JSON responses.
The client automatically decompresses the response after receiving it.
API compression improves network efficiency without changing application logic.
Pagination is the process of returning large datasets in smaller, manageable chunks instead of sending all records in a single response.
This reduces memory usage, database load, and network traffic.
Common pagination techniques include:
- Offset-Based Pagination
- Cursor-Based Pagination
- Keyset Pagination
Cursor-based pagination is generally preferred in distributed systems because it provides better performance for large datasets.
A Performance Bottleneck is any component that limits the overall performance of a distributed application.
Common bottlenecks include:
- Slow database queries
- Excessive network calls
- Blocking operations
- Insufficient thread pools
- Poor caching strategy
- High latency between services
- Resource contention
- Inefficient algorithms
Identifying and eliminating bottlenecks is essential for improving application scalability and performance.
L. Design Patterns
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
The API Gateway Pattern introduces a single entry point for all client requests in a microservices architecture.
Instead of clients communicating directly with multiple services, they send requests to the API Gateway.
The gateway handles routing, authentication, authorization, rate limiting, request transformation, response transformation, and other cross-cutting concerns before forwarding requests to the appropriate microservice.
This simplifies client communication and centralizes common functionality.
The Saga Pattern manages distributed transactions by breaking a business process into multiple local transactions.
Each microservice completes its own transaction independently.
If any step fails, compensating transactions are executed to undo previously completed operations.
Saga provides data consistency across multiple services without using distributed database transactions.
It is the preferred approach for handling distributed transactions in microservices.
The Circuit Breaker Pattern prevents repeated calls to an unhealthy service.
When failures exceed a configured threshold, the circuit opens and immediately rejects further requests instead of repeatedly calling the failing service.
After a configured recovery period, the circuit allows a limited number of requests to check whether the service has recovered.
This protects the system from cascading failures and improves resilience.
The Retry Pattern automatically retries failed operations before reporting failure to the caller.
It is useful for temporary failures such as network interruptions or brief service unavailability.
Retries should be limited and often combined with exponential backoff to avoid overwhelming already failing services.
The Retry Pattern improves reliability but should not be used for permanent failures.
The Bulkhead Pattern isolates application resources into separate compartments.
Each service or operation uses its own dedicated thread pool or resource pool.
If one compartment becomes overloaded or fails, other compartments continue functioning normally.
This prevents failures in one part of the application from affecting the entire system.
The Strangler Pattern is a migration strategy used to gradually replace a monolithic application with microservices.
Instead of rewriting the entire application at once, new functionality is developed as independent microservices while existing functionality continues running in the monolith.
Over time, more features are migrated until the monolith is completely replaced.
This reduces migration risk and allows gradual modernization.
The Sidecar Pattern deploys an additional helper component alongside a microservice within the same environment.
The sidecar handles common responsibilities such as:
- Logging
- Monitoring
- Service discovery
- Security
- Configuration
- Network proxy
This allows the main application to focus only on business logic while the sidecar manages supporting functionality.
The Sidecar Pattern is commonly used in service mesh implementations.
The Backend for Frontend (BFF) Pattern creates a dedicated backend service for each type of frontend application.
Instead of one generic backend serving all clients, each frontend, such as a web application, mobile application, or desktop application, communicates with its own backend.
Each BFF exposes APIs tailored to the specific needs of its frontend, reducing unnecessary data transfer and simplifying frontend development.
CQRS (Command Query Responsibility Segregation) is a design pattern that separates write operations from read operations.
Commands modify application data, while queries retrieve data without making changes.
This separation allows each side to be optimized independently for scalability, performance, and maintainability.
CQRS is commonly used in systems with complex business logic and high read/write workloads.
Event Sourcing is a design pattern in which every change to application data is stored as an immutable event rather than updating only the current state.
The application’s current state is reconstructed by replaying these events.
Event Sourcing provides complete audit history, supports event replay, and integrates well with CQRS and event-driven architectures.
The Transactional Outbox Pattern ensures reliable event publishing when updating a database.
Instead of directly publishing an event after a database update, the application stores the event in an Outbox table within the same database transaction.
A separate process later reads the Outbox table and publishes the events to the message broker.
This guarantees that database updates and event publishing remain consistent even if failures occur.
The Database per Service Pattern ensures that every microservice owns and manages its own database.
No other service is allowed to directly access that database.
If one service needs data owned by another service, it communicates through APIs or events rather than querying another service’s database.
This pattern provides loose coupling, independent deployment, independent schema evolution, and better scalability, making it one of the fundamental principles of microservices architecture.
M. Migration Strategies
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Migrating from a Monolith to Microservices is a gradual process where business functionalities are extracted one by one into independent microservices.
Instead of rewriting the entire application, organizations identify independent business modules, create separate microservices for them, and gradually redirect traffic from the monolith to the new services.
This approach reduces risk, allows continuous delivery, and enables both the monolith and microservices to coexist during migration.
The Strangler Fig Pattern is a migration strategy used to gradually replace a monolithic application with microservices.
Instead of replacing the entire application at once, new features or selected modules are implemented as microservices while the remaining functionality continues running in the monolith.
Over time, more modules are extracted until the monolith is completely replaced.
This approach minimizes migration risk and avoids large-scale system rewrites.
A Database Migration Strategy defines how data is moved from a monolithic database to multiple databases owned by individual microservices.
During migration, each microservice gradually becomes responsible for its own data.
The migration is typically performed in phases to ensure business continuity and avoid data loss.
The goal is to achieve the Database per Service architecture while maintaining application availability throughout the migration process.
Migrating from a shared database to separate databases should be done gradually.
Initially, microservices may continue using the shared database while ownership of specific tables is assigned to individual services.
Over time, data is copied into dedicated databases, and services are modified to access only their own databases.
Communication between services then shifts from direct database access to APIs or asynchronous events.
This gradual migration minimizes disruption and reduces migration risk.
API Versioning is the practice of maintaining multiple versions of an API so that existing clients continue working while new functionality is introduced.
For example:
- /api/v1/orders
- /api/v2/orders
Versioning allows developers to introduce changes without breaking existing client applications.
It provides backward compatibility and supports smooth API evolution.
Backward Compatibility means that newer versions of an application or API continue to support existing clients without requiring immediate changes.
When introducing new features, existing APIs should continue functioning as expected.
Breaking changes should be avoided or introduced through a new API version.
Maintaining backward compatibility reduces deployment risks and ensures uninterrupted service for existing consumers.
Zero Downtime Migration is a deployment strategy that allows application upgrades or migrations without making the application unavailable to users.
This is achieved by gradually deploying new versions while existing versions continue serving requests.
Techniques commonly used include:
- Rolling Updates
- Blue-Green Deployment
- Canary Deployment
- Feature Flags
Zero downtime migration ensures continuous availability during production deployments.
N. Testing Microservices
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Unit Testing verifies the functionality of an individual class or method in isolation.
External dependencies such as databases, REST APIs, or message brokers are replaced with mock objects.
The objective is to ensure that business logic behaves correctly under different scenarios.
Unit tests are fast, reliable, and form the foundation of the testing pyramid.
Integration Testing verifies that multiple components of an application work together correctly.
Unlike unit testing, integration tests interact with real dependencies such as databases, message brokers, or REST APIs.
The objective is to validate interactions between different application layers and external systems.
Integration testing helps identify issues that cannot be detected through isolated unit tests.
Contract Testing verifies that communication between two microservices follows an agreed contract.
The contract defines the expected request format, response format, status codes, and data structure.
Contract testing ensures that changes made by one service do not break consumers that depend on its API.
This reduces integration issues and enables independent deployments.
Consumer-Driven Contracts (CDC) are a form of contract testing where the API consumer defines its expectations for the provider.
The provider verifies that it continues to satisfy those expectations before releasing new changes.
This approach prevents breaking API changes and allows both producer and consumer teams to develop independently.
Frameworks such as Spring Cloud Contract and Pact commonly support consumer-driven contract testing.
WireMock is a tool used to simulate external HTTP services during testing.
Instead of calling actual third-party services or other microservices, WireMock returns predefined responses.
This allows tests to run consistently without depending on external systems or network availability.
WireMock is commonly used for testing REST API integrations.
Testcontainers is a Java library that starts lightweight Docker containers during automated tests.
It allows integration tests to run against real services such as:
- MySQL
- PostgreSQL
- Redis
- Kafka
- RabbitMQ
Using Testcontainers provides production-like testing environments while keeping tests isolated and repeatable.
End-to-End (E2E) Testing verifies the complete business workflow across multiple microservices from the user’s perspective.
It tests the entire application, including APIs, databases, message brokers, authentication, and inter-service communication.
For example, an end-to-end test for an e-commerce application may verify the complete flow:
- User places an order
- Payment is processed
- Inventory is updated
- Notification is sent
End-to-end testing ensures that all system components work together correctly in a production-like environment.
O. Production Scenario Questions
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
Microservices communicate either synchronously or asynchronously, depending on the business requirement.
- Synchronous communication is typically implemented using REST APIs, OpenFeign, WebClient, or gRPC. The calling service waits for an immediate response.
- Asynchronous communication uses message brokers such as Kafka or RabbitMQ. The producer publishes an event, and the consumer processes it independently without blocking the producer.
In production, REST is preferred when an immediate response is required, while Kafka or RabbitMQ is used for background processing and event-driven workflows.
REST should be used when the client needs an immediate response or confirmation.
Examples include:
- Login
- Fetching user details
- Checking product availability
- Viewing order status
Kafka should be used when services can process work independently without blocking the user.
Examples include:
- Sending notifications
- Updating inventory
- Processing analytics
- Audit logging
- Email processing
In production systems, both REST and Kafka are commonly used together. REST handles request-response interactions, while Kafka handles asynchronous business events.
Cascading failures occur when one failing service causes failures in dependent services.
To prevent cascading failures:
- Use Circuit Breakers to stop repeated calls to unhealthy services.
- Configure appropriate timeouts.
- Implement retries with exponential backoff for temporary failures.
- Use the Bulkhead Pattern to isolate resources.
- Provide fallback responses where possible.
- Apply rate limiting to protect services from excessive traffic.
- Monitor service health and performance continuously.
These techniques prevent failures from spreading across the system.
Distributed transactions are typically implemented using the Saga Pattern.
Each microservice performs its own local transaction.
If all steps succeed, the business process completes successfully.
If any step fails, compensating transactions are executed to undo previously completed operations.
Modern microservices generally avoid Two-Phase Commit (2PC) because it introduces tight coupling and performance bottlenecks.
Data consistency is achieved using eventual consistency rather than distributed database transactions.
Common approaches include:
- Saga Pattern
- Event-Driven Architecture
- Transactional Outbox Pattern
- Inbox Pattern
- Idempotent message processing
- Reliable event publishing
Each service owns its own database, and consistency is achieved through asynchronous communication.
Kafka may deliver the same message more than once, especially during failures or retries.
To avoid duplicate processing:
- Design idempotent business operations.
- Use unique event identifiers.
- Store processed message IDs in an Inbox table.
- Apply database unique constraints where appropriate.
- Commit offsets only after successful processing.
Applications should always assume that duplicate messages are possible.
Idempotency ensures that processing the same request multiple times produces the same result.
It can be implemented by:
- Using unique request IDs or idempotency keys.
- Checking whether the request has already been processed.
- Storing processed request identifiers.
- Applying unique database constraints.
- Ignoring duplicate events.
For example, if a payment request is retried due to a network issue, the payment should still be processed only once.
Secure communication between microservices is achieved using multiple security mechanisms.
Common practices include:
- HTTPS for encrypted communication.
- JWT or OAuth2 tokens for authentication.
- Service-to-Service Authentication.
- Mutual TLS (mTLS) for strong identity verification.
- API Gateway for request validation.
- Role-Based Access Control (RBAC).
- Network security policies.
These measures ensure that only trusted services can communicate with each other.
Request tracing is achieved using distributed tracing.
A unique Trace ID is generated when the request enters the system.
The same Trace ID is propagated to every downstream microservice.
Tracing frameworks such as OpenTelemetry collect trace information, while tools like Jaeger or Zipkin visualize the complete request flow.
Correlation IDs are also included in application logs, making it easier to troubleshoot production issues.
Production failures are investigated using observability tools.
A typical debugging approach includes:
- Reviewing centralized logs.
- Searching logs using Correlation IDs.
- Examining distributed traces.
- Monitoring metrics in Prometheus.
- Viewing dashboards in Grafana.
- Checking service health through Spring Boot Actuator.
- Analyzing Kubernetes events and container logs.
Combining logs, metrics, and traces helps identify the root cause quickly.
Zero-downtime deployments are achieved using deployment strategies such as:
- Rolling Updates
- Blue-Green Deployment
- Canary Deployment
Applications should also support:
- Readiness Probes
- Liveness Probes
- Graceful Shutdown
These techniques ensure that healthy instances continue serving requests while new versions are deployed.
One of the advantages of microservices is independent scalability.
If a particular service experiences high traffic, only that service is scaled.
For example, during a flash sale, the Order Service may require additional instances while the User Service continues running with the existing capacity.
In Kubernetes, this is achieved by increasing the number of replicas for the specific Deployment.
This improves resource utilization and reduces infrastructure costs.
Configuration is centrally managed using Spring Cloud Config Server.
Configuration files are stored in a Git repository and organized by environments such as:
- Development
- Testing
- Staging
- Production
Microservices retrieve their configuration during startup.
Configuration changes can be refreshed dynamically using Spring Cloud Bus and @RefreshScope, reducing the need for application restarts.
Schema evolution ensures that producers and consumers continue working even when message structures change.
Common practices include:
- Using Schema Registry.
- Maintaining backward-compatible schemas.
- Adding optional fields instead of removing existing fields.
- Avoiding incompatible changes to existing message formats.
- Versioning event schemas when necessary.
These practices allow independent deployment of producers and consumers.
API versioning allows new functionality to be introduced without breaking existing clients.
Common approaches include:
- URI Versioning (/api/v1/orders)
- Header Versioning
- Query Parameter Versioning
Most Spring Boot applications use URI versioning because it is simple, explicit, and easy to maintain.
Older API versions remain available until all consumers migrate to the new version.
Migration should be gradual rather than a complete rewrite.
A common approach is:
- Identify business domains.
- Extract one module at a time.
- Use the Strangler Fig Pattern.
- Introduce an API Gateway.
- Gradually migrate the database.
- Replace direct database access with APIs or events.
- Decommission the monolith after all modules are migrated.
This minimizes business risk and allows continuous delivery throughout the migration.
Partial failures are expected in distributed systems.
Recovery techniques include:
- Circuit Breakers
- Retries with exponential backoff
- Fallback methods
- Timeouts
- Saga compensating transactions
- Dead Letter Queues (DLQ)
- Bulkhead isolation
These mechanisms allow unaffected services to continue functioning while failed operations recover gracefully.
A complete production monitoring solution includes:
- Prometheus for metrics collection.
- Grafana for dashboards and alerting.
- ELK Stack or Loki for centralized logging.
- OpenTelemetry for telemetry collection.
- Jaeger or Zipkin for distributed tracing.
- Spring Boot Actuator for health checks.
- Kubernetes monitoring for infrastructure metrics.
Together, these tools provide visibility into application health, performance, and failures.
When a dependent service becomes unavailable, the application should fail gracefully instead of failing completely.
Common approaches include:
- Circuit Breaker
- Retry with exponential backoff
- Timeouts
- Fallback responses
- Cached responses where appropriate
- Asynchronous processing using Kafka
- User-friendly error messages
These strategies improve system resilience and user experience during outages.
A typical production design includes three independent microservices:
- Order Service – Creates and manages customer orders.
- Inventory Service – Reserves and updates product stock.
- Payment Service – Processes customer payments.
The flow is as follows:
1. The client sends a request to the API Gateway.
2. The API Gateway authenticates the user and forwards the request to the Order Service.
3. The Order Service creates the order and publishes an Order Created event to Kafka.
4. The Inventory Service consumes the event and reserves stock.
5. If inventory reservation succeeds, it publishes an Inventory Reserved event.
6. The Payment Service consumes this event and processes the payment.
7. If payment succeeds, it publishes a Payment Completed event.
8. The Order Service updates the order status to Confirmed.
9. If any step fails, the Saga Pattern executes compensating actions, such as releasing reserved inventory or cancelling the order.
Each service owns its own database, communicates using REST or Kafka where appropriate, is deployed independently, and is monitored using Prometheus, Grafana, centralized logging, and distributed tracing. This architecture provides scalability, fault tolerance, loose coupling, and high availability.
P. Frequently Asked System Design Questions
Expand a question to read the answer. Tick when you’re prepared (progress saved on this device).
A typical E-commerce system is divided into multiple independent microservices, each responsible for a single business capability.
Common microservices include:
- User Service
- Product Service
- Cart Service
- Order Service
- Payment Service
- Inventory Service
- Notification Service
The client communicates with the API Gateway, which authenticates the request and routes it to the appropriate service.
Each microservice owns its own database and communicates using REST for synchronous operations and Kafka for asynchronous events.
For example:
- Order Service creates an order.
- Inventory Service reserves stock.
- Payment Service processes payment.
- Notification Service sends confirmation emails or SMS.
The Saga Pattern is used to maintain consistency across services. Redis is used for caching, Prometheus and Grafana for monitoring, ELK/Loki for centralized logging, and Kubernetes for deployment and scaling.
An Online Banking System can be divided into the following microservices:
- Customer Service
- Account Service
- Transaction Service
- Payment Service
- Loan Service
- Notification Service
- Authentication Service
Each service maintains its own database.
Security is implemented using OAuth2, JWT, HTTPS, and mTLS for service-to-service communication.
Distributed transactions such as fund transfers are implemented using the Saga Pattern instead of distributed database transactions.
Audit logs are stored for every financial transaction.
Monitoring, centralized logging, and distributed tracing are essential because banking applications require high availability, security, and traceability.
A Food Delivery Platform typically consists of:
- User Service
- Restaurant Service
- Menu Service
- Order Service
- Payment Service
- Delivery Partner Service
- Tracking Service
- Notification Service
The customer places an order through the API Gateway.
The Order Service validates the order and publishes an event.
The Payment Service processes payment, the Restaurant Service prepares food, and the Delivery Service assigns a delivery partner.
Location tracking can use WebSockets or server push technologies for real-time updates.
Kafka is commonly used for order events, notifications, and delivery updates.
Redis is used for caching frequently accessed restaurant and menu information.
A Ticket Booking System can be designed using the following services:
- User Service
- Event/Movie Service
- Seat Management Service
- Booking Service
- Payment Service
- Notification Service
The Seat Management Service temporarily locks selected seats to prevent double booking.
The Booking Service creates the booking request.
After successful payment, the booking is confirmed and the seats are permanently reserved.
If payment fails, the temporary seat reservation expires automatically.
Distributed locking, optimistic locking, or Redis-based locking is commonly used to prevent concurrent seat allocation.
A URL Shortener can be divided into:
- URL Service
- Redirect Service
- Analytics Service
- Authentication Service
The URL Service generates unique short URLs and stores the mapping.
The Redirect Service receives the short URL and redirects users to the original URL.
The Analytics Service records click statistics, browser details, device information, and geographic location.
Redis is used to cache frequently accessed URLs for faster redirection.
Kafka can be used to asynchronously process click analytics without slowing user requests.
A Ride Booking System typically includes:
- User Service
- Driver Service
- Ride Service
- Matching Service
- Pricing Service
- Payment Service
- Notification Service
- Location Tracking Service
The customer requests a ride through the API Gateway.
The Matching Service identifies nearby drivers using location data.
After driver acceptance, the Ride Service manages ride status while the Tracking Service continuously updates driver and rider locations.
Kafka is commonly used for ride events and notifications.
Redis stores active driver locations for fast searching.
An Order Management System consists of:
- Order Service
- Product Service
- Inventory Service
- Payment Service
- Shipping Service
- Notification Service
The Order Service receives customer orders.
Inventory Service reserves stock.
Payment Service processes payment.
Shipping Service schedules delivery.
Notification Service informs customers about order status changes.
The Saga Pattern manages distributed transactions, while Kafka coordinates event-driven communication between services.
Each service owns its own database.
A Notification Service is designed as a reusable microservice responsible for delivering notifications across different channels.
It typically supports:
- SMS
- Push Notifications
- In-App Notifications
Other microservices publish notification events to Kafka or RabbitMQ.
The Notification Service consumes these events and sends notifications through external providers.
Failed notifications are retried automatically, and unsuccessful messages are moved to a Dead Letter Queue (DLQ) for later investigation.
This asynchronous approach prevents notification delays from affecting business transactions.
A Payment Service handles secure payment processing independently from other business services.
Its responsibilities include:
- Payment initiation
- Payment authorization
- Payment confirmation
- Refund processing
- Payment history
The service integrates with external payment gateways using secure APIs.
Idempotency keys ensure duplicate payment requests are not processed multiple times.
Successful payments generate events that update Order, Inventory, and Notification services.
Retries, Circuit Breakers, and audit logging improve reliability and fault tolerance.
An Inventory Service manages product stock independently.
Its responsibilities include:
- Stock availability
- Stock reservation
- Stock deduction
- Stock replenishment
- Inventory history
When an order is placed, the Inventory Service temporarily reserves stock.
After successful payment, the stock is permanently deducted.
If payment fails, the reservation is released.
Redis can be used to cache frequently requested inventory data, while Kafka enables asynchronous communication with Order and Payment services.
Optimistic locking or distributed locking techniques help prevent overselling during high-concurrency scenarios.”,”role”,”assistant