Skip to main content

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​

AdvantageExplanation
Development simplicityOne project, one IDE
Simple deploymentOne JAR/WAR to deploy
Easy testingSimple integration tests
ACID transactionsOne DB = easy transactions
Simple debuggingComplete stack trace
Local performanceNo network latency between modules
Easy refactoringIDE can rename everywhere

1.4 Disadvantages of the Monolith​

DisadvantageExplanation
All-or-nothing scalingImpossible to scale a single module
Risky deploymentOne bug = entire app down
Single stackEverything in Java (or other)
Long build timeRecompile everything each time
Team conflictsDifficult merges on a large project
Technical debtHard to modernize
Long onboardingNew 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​

AdvantageExplanation
Targeted scalingScale only the service that needs it
Fault isolationOne service down β‰  everything down
Independent deploymentDeploy 10x per day per service
PolyglotJava, Node, Python, Go... as needed
Autonomous teams"You build it, you run it"
ScalabilityAdd a service without touching others
ModernizationRewrite one service at a time

2.4 Disadvantages of Microservices​

DisadvantageExplanation
Distributed complexityDifficult debugging
Network latencyHTTP calls between services
Distributed transactionsACID impossible, Sagas needed
Eventual consistencyData not immediately synchronized
Complex operationsMonitoring, logging, tracing
Infrastructure costMore servers, more DBs
OverheadEach service = memory overhead

3. Detailed Comparison​

3.1 Comparative Table​

CriterionMonolithMicroservices
Initial complexityLowHigh
Long-term complexityHighManaged
DeploymentAll or nothingIndependent
ScalingVerticalHorizontal
TechnologySinglePolyglot
TeamOne teamTeams per service
DatabaseSharedPer service
TransactionsSimple ACIDSagas (complex)
DebuggingSimpleDistributed (complex)
LatencyLowHigher
Initial costLowHigh
Time to marketFast initiallySlower 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:

  1. Service starts β†’ registers with Eureka
  2. Client wants to call Service A
  3. Client asks Eureka: "Where is Service A?"
  4. Eureka returns the list of instances
  5. Client chooses an instance (client-side load balancing)
  6. 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​

#FactorDescriptionSpring Boot Application
1CodebaseOne repo = one appOne Git repo per service
2DependenciesDeclare explicitlypom.xml / build.gradle
3ConfigConfig in the environmentapplication.yml + env vars
4Backing ServicesTreat as attached resourcesConfigurable DataSource
5Build, Release, RunSeparate stagesCI/CD pipeline
6ProcessesStatelessSession in Redis
7Port BindingExport via portEmbedded Tomcat
8ConcurrencyScale via processesMultiple instances
9DisposabilityFast startup/shutdownGraceful shutdown
10Dev/Prod ParitySimilar environmentsDocker
11LogsTreat as streamsstdout β†’ collector
12Admin ProcessesOne-off admin tasksSpring 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​

  1. Identify Bounded Contexts in the monolith
  2. Start with the least risky (e.g., notifications)
  3. Extract into a new service
  4. Route via API Gateway
  5. Remove old code from the monolith
  6. 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.


Next Chapter: Service Discovery with Eureka β†’

Back to Module 13