System Design Using Microservices Architecture


Introduction
In software development landscape, systems are becoming more complex and need to scale quickly and efficiently. To meet these demands, many organisations have shifted from monolithic architecture to microservices architecture.
This article breaks down the technical details of microservices architecture. How it works, it’s advantages, and how different components interact.
Microservices Architecture
Microservices architecture is an architectural style that structures an application as a collection of small, autonomous services, modelled around a business domain. Each service is self-contained, independently deployable, and communicates with other services through well-defined APIs, typically over a network using lightweight protocols (such as HTTP or messaging queues). This approach contrasts with the traditional monolithic architecture where the entire application is built as a single, unified unit.
Key Characteristics
Independently deployable: Each microservice can be updated without redeploying the entire system.
Automated Deployment and Monitoring: DevOps practices, such as Continuous Integration and Continuous Deployment (CI/CD), streamline development and operational efficiency.
Loosely coupled: Services interact through APIs, reducing dependencies.
Organised around Business Capabilities: For example, in an e-commerce application, separate microservices might handle orders, payments, and inventory.
Owned by small teams: Each team manages a service from development to deployment.
Scalability: Each service can scale individually based on demand, optimising resource usage.
Fault Isolation: Failure in one service doesn't necessarily bring down the entire system.
Technology Diversity: Different services can use different programming languages and frameworks.
Why Microservices?
Feature | Benefit |
Scalability | Scale individual services independently based on demand |
Flexibility | Use different technologies/languages per service |
Resilience | Failure in one service doesn’t crash the entire system |
Faster deployments | Teams can deploy updates without waiting on other teams |
Better organisation | Easier to manage in large teams with domain-based ownership |
Microservices vs. Monolithic Architecture
Aspect | Monolithic Architecture | Microservices Architecture |
Architecture | Single-tier, tightly coupled | Multi-tier, loosely coupled |
Codebase | Single large codebase | Multiple small codebases |
Deployment | Entire application at once | Independent service deployment |
Scalability | Harder to scale | Easier to scale horizontally |
Technology Stack | Limited flexibility | Polyglot programming possible |
Fault Tolerance | Entire system affected | Isolated failures |
Maintenance | Simpler initially, harder as it grows | More effort upfront, easier long-term |
Core Components of a Microservices Architecture
1. Client Applications
What it is: The user-facing front-end of the system (e.g., a Single Page Application running in a browser or a native mobile app).
Role: It provides the user interface. Crucially, it is decoupled from the backend microservices and communicates through a single, well-defined entry point: the API Gateway.
Examples: React, Angular, Vue.js, iOS (Swift), Android (Kotlin).
2. API Gateway
What iti is: A single entry point for all client requests. It acts as a reverse proxy, routing requests to the appropriate backend microservice.
Role:
Routing: Directs incoming traffic to the correct service.
Security: Handles authentication and authorization, often by integrating with an Identity Provider. It can offload this concern from the individual services.
Cross-Cutting Concerns: Manages rate limiting, SSL termination, caching, and request/response transformation.
Resilience: Can implement patterns like Circuit Breakers and Retries.
Examples: Kong, Spring Cloud Gateway, Amazon API Gateway, NGINX.
3. Microservices
What it is: The heart of the architecture. Each service is a small, independent application responsible for a specific business capability (e.g., managing users, processing orders).
Role:
Single Responsibility: Owns a distinct piece of business logic.
Autonomy: Can be developed, deployed, and scaled independently of other services.
Decentralised Data: Manages its own database, ensuring loose coupling.
Examples: User Service, Product Service, Order Service.
4. Decentralised Data Storage
What it is:
Database per Service: Each microservice has its own private database that only it can access directly. This avoids the "shared database" anti-pattern.
Event Sourcing: Instead of modifying records directly, all state changes are captured as immutable events.
CQRS (Command Query Responsibility Segregation): Separates the read and write models, improving performance.
Role: This is a core principle that ensures loose coupling. If one service needs data from another, it must go through that service's public API, not by accessing its database directly. This allows each service to choose the best type of database for its needs (e.g., a relational DB for transactions, a NoSQL DB for product catalogs).
Examples: PostgreSQL, MongoDB, MySQL, Cassandra.
5. Service Discovery
What it is: A "phone book" for your services. In a dynamic environment where services are constantly being scaled up and down, their network locations (IP addresses, ports) change.
Role:
Registration: When a service starts up, it registers its location with the discovery server.
Discovery: When one service (or the API Gateway) needs to call another, it asks the discovery server for the target service's current location.
Examples: Netflix Eureka, Consul, etcd.
6. Security Considerations:
Due to the distributed nature of microservices, security becomes a priority.
API Gateway: Manages authentication, rate limiting, and request routing.
OAuth2 & JWT: Secure API communications using authentication tokens.
Service Mesh (Istio, Linkerd): Implements security policies and observability between microservices.
7. Configuration Server
What it is: A centralised place to manage external configuration for all services across different environments (development, staging, production).
Role: It allows you to change configuration (e.g., database connection strings, feature flags) without redeploying the service. Services fetch their configuration from this server on startup.
Examples: Spring Cloud Config, HashiCorp Consul KV, AWS Parameter Store.
8. Message Broker / Event Bus (Asynchronous Communication)
What it is: A component that enables asynchronous communication between services using patterns like message queues or publish/subscribe.
Role: It decouples services. For example, when an order is placed, the Order Service can publish an OrderCreated event. The Payment Service and Notification Service can then subscribe to this event and react accordingly, without the Order Service needing to know about them. This improves resilience and scalability.
Examples: RabbitMQ, Apache Kafka, AWS SQS/SNS.
9. Identity Provider / Auth Server
What it is: A centralised service responsible for authenticating users and issuing security tokens (e.g., JSON Web Tokens - JWTs).
Role: It offloads the complexity of user authentication from the individual services. The API Gateway typically validates tokens with this server before forwarding requests to backend services.
Examples: Keycloak, Auth0, Okta, IdentityServer.
10. Container Orchestration
What it is: The underlying platform that automates the deployment, scaling, and management of the containerised services.
Role: It handles an immense amount of operational complexity, including service placement, health checks, self-healing (restarting failed containers), and scaling services up or down based on load.
Examples: Kubernetes (the de facto standard), Docker Swarm, Amazon ECS.
11. Observability Stack
Because the system is distributed, monitoring becomes critical. Observability is typically broken down into three pillars:
Log Aggregator: Collects logs from all services into a centralised, searchable location. (e.g., ELK Stack - Elasticsearch, Logstash, Kibana).
Metrics Server: Collects and visualizes time-series data (metrics) like CPU usage, memory, response times, and error rates from each service. (e.g., Prometheus with Grafana for visualisation).
Distributed Tracing: Traces a single request as it flows through multiple services, allowing you to visualise the entire call chain and pinpoint bottlenecks or failures. (e.g., Jaeger, Zipkin).
12. Inter-Service Communication
Since microservices are distributed and loosely coupled, they require communication mechanisms to interact. Some common communication patterns include.
Synchronous
RESTful APIs: Services expose endpoints via HTTP to allow other services to send requests.
gRPC: A high-performance RPC framework that allows services to communicate efficiently.
Asynchronous
Message Queues (e.g., Kafka, RabbitMQ): Asynchronous messaging enables event-driven communication.
Event-Driven Architecture: Services communicate through events rather than direct API calls, enhancing scalability.
13. Deployment and DevOps
Microservices are deployed using CI/CD pipelines, ensuring faster development cycles. Automates testing, building, and deployment for each microservice using tools like Jenkins, GitLab CI, or GitHub Actions. Common deployment methods include
Containerisation (Docker, Kubernetes): Packages services into lightweight, portable containers.
Orchestration (Kubernetes, AWS ECS): Manages and scales containerised services efficiently.
Example of Online Retail System
Let’s take an online store as an example. Here’s how we can split it into microservices:
User Service – Handles user authentication and profiles
Product Service – Manages product listings
Order Service – Processes customer orders
Payment Service – Handles transactions
Inventory Service – Tracks stock availability
Notification Service – Sends emails or messages to customers
Each service communicates over the network through REST APIs or a message broker and is deployed independently.
Typical Workflow Example (Placing an Order)
A user on a Web App User Service clicks "Place Order." The app sends an API request to the API Gateway.
The API Gateway intercepts the request. It first calls the Identity Provider to validate the user's authentication token.
Once authenticated, the Gateway consults the Service Discovery server to find the network location of the Order Service.
The Gateway forwards the request to the Order Service.
The Order Service might make synchronous calls to the User Service (to get shipping details) and the Product Service (to check stock). Each of these calls also involves a lookup via Service Discovery.
The Order Service saves the new order to its private Order DB.
The Order Service then publishes an OrderCreated event to the Message Broker.
The Payment Service and a Notification Service are subscribed to this event. They consume the message and begin their independent processes (charging the credit card, sending a confirmation email).
Throughout this entire process, all services (Gateway, Order, User, etc.) are sending logs, metrics, and traces to the Observability Stack, allowing developers to monitor the system's health and performance. All of this runs on the Container Orchestration platform.
Challenges of Microservices
While microservices offer many benefits, they also introduce complexity:
Distributed Systems Complexity: Network communication adds latency and potential points of failure.
Data Consistency: Maintaining consistency across services without a shared database can be tricky (eventual consistency is common).
DevOps Overhead: Requires robust infrastructure for deployment, monitoring, and scaling.
Debugging Difficulty: Tracing errors across multiple services can be challenging.
Service Discovery: Dynamically locating services as they scale requires registry mechanisms.
Network Latency: Inter-service communication over a distributed network introduces delays.
Best Practices
Design services around business capabilities, not technical layers.
Use domain-driven design (DDD) to define service boundaries.
Use bounded contexts to define clear service boundaries.
Keep services small and cohesive – follow the single responsibility principle.
Implement centralised logging and monitoring.
Implement circuit breakers (e.g., Netflix Hystrix) to prevent cascading failures.
Use retry mechanisms and fallback strategies for handling failures gracefully.
Use load balancers to distribute traffic efficiently.
Use containerisation (Docker, Kubernetes) for consistent deployments across various environments.
Conclusion
Microservices architecture offers a powerful way to build scalable, maintainable, and resilient systems. By breaking an application into smaller services, teams can move faster, scale more efficiently, and better align with business needs. However, it also demands careful planning, strong DevOps practices, and thoughtful design.
Subscribe to my newsletter
Read articles from Sandeep Choudhary directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
