Topic Overview
Monolith vs Microservices
Compare monolithic and microservices architectures. Understand when to choose each, migration strategies, and the trade-offs involved in architectural decisions.
Choosing between monolithic and microservices architecture is a critical decision. Each has trade-offs, and the right choice depends on your specific requirements.
Monolithic Architecture
Monolith is a single, unified application.
Characteristics
- Single codebase: All code in one repository
- Single deployment: Deploy entire application
- Shared database: All modules use same database
- Tight coupling: Modules depend on each other
- Single technology: One technology stack
Advantages
- Simple: Easier to develop and test
- Performance: No network calls between modules
- Transactions: Easy ACID transactions
- Debugging: Easier to debug (single process)
- Deployment: Simple deployment
Disadvantages
- Scaling: Must scale entire application
- Technology lock-in: Hard to change technology
- Team coordination: Large teams work on same codebase
- Deployment risk: One bug affects entire system
- Fault isolation: Failure affects everything
Microservices Architecture
Microservices are small, independent services.
Characteristics
- Multiple services: Each service is separate
- Independent deployment: Deploy services separately
- Database per service: Each service has its own database
- Loose coupling: Services communicate via APIs
- Technology diversity: Different technologies per service
Advantages
- Scalability: Scale services independently
- Technology diversity: Use best tool for each service
- Fault isolation: Failure in one service doesn't cascade
- Team autonomy: Teams work independently
- Faster deployment: Deploy services separately
Disadvantages
- Complexity: Distributed system complexity
- Network overhead: Network calls between services
- Data consistency: Distributed transactions are hard
- Testing: More complex testing
- Deployment: More services to deploy
When to Choose Each
Choose Monolith When:
- Small team: Few developers
- Simple application: Not complex
- Fast development: Need to move quickly
- Strong consistency: Need ACID transactions
- Low scale: Not expecting high traffic
Choose Microservices When:
- Large team: Multiple teams
- Complex application: Many features
- Different scaling needs: Services scale differently
- Technology diversity: Need different technologies
- High scale: Expecting high traffic
Migration Strategy
Start with Monolith
1. Build monolith
2. Identify boundaries
3. Extract services gradually
4. Migrate to microservices
Benefits:
- Learn domain first
- Identify service boundaries
- Avoid premature optimization
Examples
Monolithic Application
// Single application
class ECommerceApp {
private userService: UserService;
private orderService: OrderService;
private paymentService: PaymentService;
async createOrder(userId: string, items: Item[]): Promise<Order> {
// All in same process
const user = await this.userService.getUser(userId);
const order = await this.orderService.createOrder(userId, items);
await this.paymentService.processPayment(order.id);
return order;
}
}
Microservices Application
// Separate services
class OrderService {
async createOrder(userId: string, items: Item[]): Promise<Order> {
// Call user service (HTTP/gRPC)
const user = await this.userServiceClient.getUser(userId);
// Create order
const order = await this.database.createOrder(userId, items);
// Call payment service
await this.paymentServiceClient.processPayment(order.id);
return order;
}
}
Common Pitfalls
- Premature microservices: Too early, adds complexity. Fix: Start with monolith, extract when needed
- Over-microservicing: Too many small services. Fix: Start with fewer, larger services
- Shared database: Services sharing database. Fix: Database per service
- Synchronous calls everywhere: Creating call chains. Fix: Use async messaging
Interview Questions
Beginner
Q: What is the difference between monolithic and microservices architecture?
A:
Monolithic Architecture:
- Single application: All code in one codebase
- Single deployment: Deploy entire application
- Shared database: All modules use same database
- Tight coupling: Modules depend on each other
Microservices Architecture:
- Multiple services: Each service is separate
- Independent deployment: Deploy services separately
- Database per service: Each service has its own database
- Loose coupling: Services communicate via APIs
Key Differences:
| Feature | Monolith | Microservices |
|---|---|---|
| Codebase | Single | Multiple |
| Deployment | Single unit | Independent |
| Database | Shared | Per service |
| Scaling | Scale entire app | Scale services independently |
| Complexity | Lower | Higher |
When to use:
- Monolith: Small team, simple app, fast development
- Microservices: Large team, complex app, different scaling needs
Intermediate
Q: Explain the trade-offs between monolith and microservices. When would you migrate from monolith to microservices?
A:
Monolith Trade-offs:
Advantages:
- Simple: Easier to develop, test, debug
- Performance: No network calls between modules
- Transactions: Easy ACID transactions
- Deployment: Simple deployment
Disadvantages:
- Scaling: Must scale entire application
- Technology lock-in: Hard to change
- Team coordination: Large teams on same codebase
- Fault isolation: Failure affects everything
Microservices Trade-offs:
Advantages:
- Scalability: Scale services independently
- Technology diversity: Different technologies
- Fault isolation: Failure doesn't cascade
- Team autonomy: Teams work independently
Disadvantages:
- Complexity: Distributed system complexity
- Network overhead: Network calls between services
- Data consistency: Distributed transactions hard
- Testing: More complex
Migration Strategy:
1. Start with monolith
2. Identify service boundaries (domain-driven design)
3. Extract services gradually
4. Migrate to microservices
When to migrate:
- Team size: Multiple teams need independence
- Scaling needs: Different services scale differently
- Technology needs: Need different technologies
- Complexity: Monolith becoming too complex
Senior
Q: Design a migration strategy from monolith to microservices for a large e-commerce platform. How do you identify service boundaries, handle data migration, and ensure zero downtime?
A:
class MonolithToMicroservicesMigration {
private monolith: Monolith;
private services: Map<string, Service>;
private dataMigrator: DataMigrator;
constructor() {
this.monolith = new Monolith();
this.services = new Map();
this.dataMigrator = new DataMigrator();
}
// 1. Identify Service Boundaries
identifyBoundaries(): ServiceBoundary[] {
// Use domain-driven design
// Identify bounded contexts
return [
{ name: 'user', domain: 'User Management' },
{ name: 'order', domain: 'Order Processing' },
{ name: 'payment', domain: 'Payment Processing' },
{ name: 'inventory', domain: 'Inventory Management' }
];
}
// 2. Strangler Fig Pattern
async migrateService(serviceName: string): Promise<void> {
// Step 1: Extract service (parallel to monolith)
const service = await this.extractService(serviceName);
// Step 2: Route traffic gradually
await this.routeTrafficGradually(serviceName, service);
// Step 3: Migrate data
await this.migrateData(serviceName);
// Step 4: Decommission monolith code
await this.decommissionMonolithCode(serviceName);
}
// 3. Gradual Traffic Routing
async routeTrafficGradually(serviceName: string, service: Service): Promise<void> {
// Start with 0% traffic
let percentage = 0;
while (percentage < 100) {
// Route percentage to new service
await this.loadBalancer.setRouting(serviceName, {
monolith: 100 - percentage,
microservice: percentage
});
// Monitor
await this.monitor(service);
// Increase gradually
percentage += 10;
await this.sleep(60000); // 1 minute
}
}
// 4. Data Migration
class DataMigrator {
async migrateData(serviceName: string): Promise<void> {
// Dual write: Write to both monolith and service
await this.enableDualWrite(serviceName);
// Sync existing data
await this.syncExistingData(serviceName);
// Verify data consistency
await this.verifyConsistency(serviceName);
// Switch reads to service
await this.switchReads(serviceName);
// Stop writing to monolith
await this.disableMonolithWrite(serviceName);
}
}
// 5. Zero Downtime
ensureZeroDowntime(): void {
// Run services in parallel
// Gradual traffic migration
// Health checks
// Rollback capability
}
}
Features:
- Service boundaries: Domain-driven design
- Strangler fig: Gradual migration
- Traffic routing: Gradual traffic shift
- Data migration: Dual write, sync, verify
- Zero downtime: Parallel running, rollback
Key Takeaways
- Monolith: Single application, simpler, easier to develop
- Microservices: Multiple services, scalable, more complex
- Trade-offs: Monolith simpler, microservices more scalable
- When to choose: Monolith for small/medium, microservices for large/complex
- Migration: Start with monolith, extract services gradually
- Best practices: Don't over-engineer, start simple, migrate when needed