Chapter 1: Introduction to Microservices
π― Chapter Objectivesβ
- Understand the difference between monolith and microservices
- Identify the advantages and disadvantages of each approach
- Discover the essential microservices patterns
- Master the principles of the 12-Factor App
- Know when to choose which architecture
1. Monolithic Architectureβ
1.1 Definitionβ
A monolith is an application where all functionalities are deployed together in a single process.
1.2 Example: Monolithic E-Commerceβ
// Typical structure of a monolith
src/main/java/com/ecommerce/
βββ EcommerceApplication.java
βββ config/
β βββ SecurityConfig.java
βββ controller/
β βββ UserController.java
β βββ ProductController.java
β βββ OrderController.java
β βββ PaymentController.java
β βββ InventoryController.java
βββ service/
β βββ UserService.java
β βββ ProductService.java
β βββ OrderService.java
β βββ PaymentService.java
β βββ InventoryService.java
βββ repository/
β βββ UserRepository.java
β βββ ProductRepository.java
β βββ OrderRepository.java
β βββ ...
βββ model/
βββ User.java
βββ Product.java
βββ Order.java
βββ ...
1.3 Advantages of the Monolithβ
| Advantage | Explanation |
|---|---|
| Development simplicity | One project, one IDE |
| Simple deployment | One JAR/WAR to deploy |
| Easy testing | Simple integration tests |
| ACID transactions | One DB = easy transactions |
| Simple debugging | Complete stack trace |
| Local performance | No network latency between modules |
| Easy refactoring | IDE can rename everywhere |
1.4 Disadvantages of the Monolithβ
| Disadvantage | Explanation |
|---|---|
| All-or-nothing scaling | Impossible to scale a single module |
| Risky deployment | One bug = entire app down |
| Single stack | Everything in Java (or other) |
| Long build time | Recompile everything each time |
| Team conflicts | Difficult merges on a large project |
| Technical debt | Hard to modernize |
| Long onboarding | New dev must understand everything |
1.5 When the Monolith Becomes Problematicβ
Year 1: Startup
- 3 developers
- 50K lines of code
- Deployment: 5 minutes
β PERFECT Monolith β
Year 3: Growth
- 30 developers
- 500K lines of code
- Deployment: 45 minutes
- Daily Git conflicts
β Monolith becomes a bottleneck β οΈ
Year 5: Scale
- 100 developers
- 2M lines of code
- Deployment: 2 hours
- Black Friday: everything crashes
β Monolith = "Big Ball of Mud" β
2. Microservices Architectureβ
2.1 Definitionβ
Microservices are an approach where the application is divided into small autonomous services, each:
- Deployable independently
- With its own database
- Communicating via API (HTTP/REST or messaging)
- Managed by a small team
2.2 Key Characteristicsβ
1. Single Responsibilityβ
Each service does ONE thing well:
- user-service: user management ONLY
- order-service: order management ONLY
- inventory-service: stock management ONLY
2. Autonomyβ
Each service:
- Has its own Git repo
- Has its own DB (Database per Service)
- Has its own CI/CD pipeline
- Can be deployed alone
- Can use a different technology
3. Communicationβ
Synchronous (HTTP/REST):
- Client waits for the response
- Simple but coupling
Asynchronous (Messaging):
- Fire and forget
- Decoupled but complex
2.3 Advantages of Microservicesβ
| Advantage | Explanation |
|---|---|
| Targeted scaling | Scale only the service that needs it |
| Fault isolation | One service down β everything down |
| Independent deployment | Deploy 10x per day per service |
| Polyglot | Java, Node, Python, Go... as needed |
| Autonomous teams | "You build it, you run it" |
| Scalability | Add a service without touching others |
| Modernization | Rewrite one service at a time |
2.4 Disadvantages of Microservicesβ
| Disadvantage | Explanation |
|---|---|
| Distributed complexity | Difficult debugging |
| Network latency | HTTP calls between services |
| Distributed transactions | ACID impossible, Sagas needed |
| Eventual consistency | Data not immediately synchronized |
| Complex operations | Monitoring, logging, tracing |
| Infrastructure cost | More servers, more DBs |
| Overhead | Each service = memory overhead |
3. Detailed Comparisonβ
3.1 Comparative Tableβ
| Criterion | Monolith | Microservices |
|---|---|---|
| Initial complexity | Low | High |
| Long-term complexity | High | Managed |
| Deployment | All or nothing | Independent |
| Scaling | Vertical | Horizontal |
| Technology | Single | Polyglot |
| Team | One team | Teams per service |
| Database | Shared | Per service |
| Transactions | Simple ACID | Sagas (complex) |
| Debugging | Simple | Distributed (complex) |
| Latency | Low | Higher |
| Initial cost | Low | High |
| Time to market | Fast initially | Slower initially |
3.2 Decision Criteriaβ
4. Essential Microservices Patternsβ
4.1 API Gateway Patternβ
Responsibilities:
- Request routing
- Authentication/Authorization
- Rate Limiting
- Load Balancing
- SSL Termination
- Caching
- Request/Response transformation
4.2 Service Discovery Patternβ
How it works:
- Service starts β registers with Eureka
- Client wants to call Service A
- Client asks Eureka: "Where is Service A?"
- Eureka returns the list of instances
- Client chooses an instance (client-side load balancing)
- Client calls the instance directly
4.3 Circuit Breaker Patternβ
States:
- CLOSED: Normal, calls pass
- OPEN: Too many failures, calls blocked β fallback
- HALF-OPEN: Test if the service is back
// Example with Resilience4j
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public User getUser(Long id) {
return userServiceClient.getUser(id); // Remote call
}
public User getUserFallback(Long id, Exception ex) {
return new User(id, "Unknown", "Service temporarily unavailable");
}
4.4 Saga Pattern (Distributed Transactions)β
In a monolith:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order); // β
inventoryService.reserve(order); // β
paymentService.charge(order); // β Failure β ROLLBACK everything
}
With microservices (Saga Choreography):
4.5 Event Sourcing & CQRSβ
Events instead of states:
// Instead of: UPDATE orders SET status = 'SHIPPED'
// Store events:
OrderCreated { orderId: 1, items: [...], timestamp: ... }
PaymentReceived { orderId: 1, amount: 100, timestamp: ... }
OrderShipped { orderId: 1, trackingNumber: "ABC123", timestamp: ... }
5. The Twelve-Factor Appβ
The 12 factors are principles for building cloud-native applications:
The 12 Factorsβ
| # | Factor | Description | Spring Boot Application |
|---|---|---|---|
| 1 | Codebase | One repo = one app | One Git repo per service |
| 2 | Dependencies | Declare explicitly | pom.xml / build.gradle |
| 3 | Config | Config in the environment | application.yml + env vars |
| 4 | Backing Services | Treat as attached resources | Configurable DataSource |
| 5 | Build, Release, Run | Separate stages | CI/CD pipeline |
| 6 | Processes | Stateless | Session in Redis |
| 7 | Port Binding | Export via port | Embedded Tomcat |
| 8 | Concurrency | Scale via processes | Multiple instances |
| 9 | Disposability | Fast startup/shutdown | Graceful shutdown |
| 10 | Dev/Prod Parity | Similar environments | Docker |
| 11 | Logs | Treat as streams | stdout β collector |
| 12 | Admin Processes | One-off admin tasks | Spring Batch |
Example: Factor 3 - Configurationβ
# β Bad: Hardcoded config
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: secret123
# β
Good: Externalized config
spring:
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
Example: Factor 6 - Stateless Processesβ
// β Bad: State in memory
@RestController
public class CartController {
private Map<Long, Cart> carts = new HashMap<>(); // Local state!
@PostMapping("/cart/{userId}/add")
public void addItem(@PathVariable Long userId, @RequestBody Item item) {
carts.computeIfAbsent(userId, Cart::new).add(item);
}
}
// β
Good: Externalized state (Redis)
@RestController
public class CartController {
private final RedisTemplate<String, Cart> redis;
@PostMapping("/cart/{userId}/add")
public void addItem(@PathVariable Long userId, @RequestBody Item item) {
String key = "cart:" + userId;
Cart cart = redis.opsForValue().get(key);
if (cart == null) cart = new Cart();
cart.add(item);
redis.opsForValue().set(key, cart);
}
}
6. Domain-Driven Design (DDD) and Microservicesβ
6.1 Bounded Contextβ
The Bounded Context defines the boundaries of a microservice:
6.2 Same Concept, Different Contextsβ
// In Sales Context
public class Customer {
private Long id;
private String name;
private String email;
private List<Order> orders;
private LoyaltyLevel loyaltyLevel;
}
// In Shipping Context
public class Customer {
private Long id;
private String name;
private Address shippingAddress;
private String phoneNumber;
}
// In Billing Context
public class Customer {
private Long id;
private String name;
private PaymentMethod defaultPaymentMethod;
private BillingAddress billingAddress;
}
7. Anti-Patterns to Avoidβ
7.1 Distributed Monolithβ
β ANTI-PATTERN: Distributed monolith
- 10 services that MUST be deployed together
- Shared DB schema
- Strong coupling between services
- Worst of both worlds!
7.2 Nano-Servicesβ
β ANTI-PATTERN: Services too small
- One service per entity
- UserService, UserAddressService, UserPreferenceService
- Huge overhead
- Impossible transactions
7.3 Shared Databaseβ
β ANTI-PATTERN: Shared database
Service A βββ¬ββ> PostgreSQL <βββ¬ββ Service B
β β
βββ> orders table <β
Problem:
- Strong coupling via schema
- Impossible to modify a table
- Tempting cross transactions
7.4 Sync Everywhereβ
β ANTI-PATTERN: Everything synchronous
Order Service
β HTTP sync
Inventory Service
β HTTP sync
Payment Service
β HTTP sync
Notification Service
Problem:
- Cumulative latency
- One slow service = everything slow
- Cascade failures
8. Migration Monolith β Microservicesβ
8.1 Strategy: Strangler Fig Patternβ
8.2 Migration Stepsβ
- Identify Bounded Contexts in the monolith
- Start with the least risky (e.g., notifications)
- Extract into a new service
- Route via API Gateway
- Remove old code from the monolith
- Repeat for the next context
9. Summaryβ
Key Pointsβ
β
Monolith = Simple but doesn't scale
β
Microservices = Complex but flexible
β
Start monolith if possible
β
Migrate when the monolith becomes a bottleneck
β
12-Factor App = cloud-native best practices
β
DDD = helps define boundaries
β
Patterns = Circuit Breaker, Saga, Event Sourcing
Checklist Before Microservicesβ
- Experienced DevOps team?
- Kubernetes/Docker infrastructure?
- Centralized monitoring and logging?
- Mature CI/CD?
- Well-defined Bounded Contexts?
- Real scaling issues?
10. Validation Quizβ
Question 1β
Which pattern helps prevent cascade failures between services?
See the answer
Circuit Breaker - It detects failures and "opens" the circuit to return a fallback immediately instead of waiting for timeouts.
Question 2β
Why is "Database per Service" important?
See the answer
- Decoupling: Each service can evolve its schema independently
- Autonomy: No dependency on a central DBA
- Technology: PostgreSQL for one service, MongoDB for another
- Avoids hidden coupling via shared schema
Question 3β
What is a "Distributed Monolith"?
See the answer
An anti-pattern where there are multiple services but:
- They must be deployed together
- They share a database
- They are strongly coupled
You have the complexity of microservices WITHOUT the benefits.