Microservices Architecture
Build a product catalog system with two communicating microservices
What You Will Learn
In this practical work, you'll build a simple microservices system from scratch. You'll understand:
- What microservices are - Small, independent services that work together
- Service communication - How services talk to each other over HTTP
- Service discovery - How services find each other (Eureka)
- API Gateway - A single entry point for all services
- Resilience - Handling failures gracefully (Circuit Breaker)
Understanding Microservices
Monolith: One big application with everything inside. Like a house where kitchen, bedroom, and bathroom are all connected - if you renovate the kitchen, you might affect the bathroom.
Microservices: Many small applications that communicate. Like apartments in a building - each is independent. You can renovate one without affecting others.
What we'll build:
User Request
│
▼
┌─────────────┐
│ API Gateway │ ← Single entry point (port 8080)
│ (Gateway) │
└─────────────┘
│
┌────┴────┐
│ │
▼ ▼
┌────────┐ ┌────────────┐
│Product │ │ Inventory │ ← Independent services
│Service │ │ Service │
│ :8081 │ │ :8082 │
└────────┘ └────────────┘
│ │
└──────┬──────┘
│
┌──────────────┐
│Eureka Server │ ← Service registry (port 8761)
│ (Discovery)│ "Phone book" of services
└──────────────┘
Prerequisites
- Java 17+ and Maven installed
- Understanding of REST APIs (Practical Work 4)
- Completed Practical Work 3 (JPA basics)
Part 1: Create the Parent Project
Step 1.1: Project Structure
We'll create a multi-module Maven project. Create this folder structure:
product-catalog/
├── pom.xml (parent POM)
├── eureka-server/ (service discovery)
│ ├── pom.xml
│ └── src/main/...
├── product-service/ (manages products)
│ ├── pom.xml
│ └── src/main/...
├── inventory-service/ (manages stock)
│ ├── pom.xml
│ └── src/main/...
└── api-gateway/ (single entry point)
├── pom.xml
└── src/main/...
Step 1.2: Create Parent pom.xml
Create product-catalog/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- (#1:Parent manages versions for all child modules) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
</parent>
<groupId>com.example</groupId>
<artifactId>product-catalog</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- (#2:pom = this is a parent) -->
<!-- (#3:List all child modules) -->
<modules>
<module>eureka-server</module>
<module>product-service</module>
<module>inventory-service</module>
<module>api-gateway</module>
</modules>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<!-- (#4:Spring Cloud dependency management) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Spring Cloud is a collection of tools for building microservices:
- Eureka: Service discovery (like a phone book)
- Gateway: Single entry point for all services
- OpenFeign: Easy HTTP calls between services
- Resilience4j: Handle failures gracefully
Part 2: Create Eureka Server (Service Discovery)
Imagine you're in a new city trying to find a restaurant. You could:
- Know the exact address (hard-coded URLs) - but what if they move?
- Ask a local guide who knows all restaurants (Service Discovery!)
Eureka is that guide. Services register with Eureka, and other services ask Eureka: "Where is the product-service?"
Step 2.1: Create eureka-server/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>product-catalog</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>eureka-server</artifactId>
<dependencies>
<!-- (#1:Eureka Server dependency) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 2.2: Create Main Application
Create eureka-server/src/main/java/com/example/eureka/EurekaServerApplication.java:
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // (#1:This annotation makes this a Eureka Server!)
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Step 2.3: Configure Eureka Server
Create eureka-server/src/main/resources/application.yml:
server:
port: 8761 # (#1:Standard Eureka port)
eureka:
client:
register-with-eureka: false # (#2:Don't register itself)
fetch-registry: false # (#3:Don't fetch from itself)
spring:
application:
name: eureka-server
YAML is more readable for nested configurations. Both work equally well!
Step 2.4: Test Eureka Server
cd product-catalog/eureka-server
mvn spring-boot:run
Open http://localhost:8761 - you'll see the Eureka dashboard. The "Instances currently registered" section will be empty (no services yet).
Part 3: Create Product Service
Step 3.1: Create product-service/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>product-catalog</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>product-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- (#1:Eureka Client - registers with Eureka Server) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 3.2: Create Product Entity
Create product-service/src/main/java/com/example/product/entity/Product.java:
package com.example.product.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Column(nullable = false)
private BigDecimal price;
@Column(unique = true, nullable = false)
private String sku; // (#1:Stock Keeping Unit - unique product code)
// Constructors
public Product() {}
public Product(String name, String description, BigDecimal price, String sku) {
this.name = name;
this.description = description;
this.price = price;
this.sku = sku;
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
public String getSku() { return sku; }
public void setSku(String sku) { this.sku = sku; }
}
Step 3.3: Create Repository
Create product-service/src/main/java/com/example/product/repository/ProductRepository.java:
package com.example.product.repository;
import com.example.product.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<Product> findBySku(String sku);
}
Step 3.4: Create DTO
Create product-service/src/main/java/com/example/product/dto/ProductResponse.java:
package com.example.product.dto;
import com.example.product.entity.Product;
import java.math.BigDecimal;
public class ProductResponse {
private Long id;
private String name;
private String description;
private BigDecimal price;
private String sku;
private Integer stockQuantity; // (#1:Will be filled from Inventory Service)
private boolean inStock;
public static ProductResponse fromEntity(Product product) {
ProductResponse response = new ProductResponse();
response.id = product.getId();
response.name = product.getName();
response.description = product.getDescription();
response.price = product.getPrice();
response.sku = product.getSku();
return response;
}
// Getters and Setters
public Long getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public BigDecimal getPrice() { return price; }
public String getSku() { return sku; }
public Integer getStockQuantity() { return stockQuantity; }
public void setStockQuantity(Integer stockQuantity) {
this.stockQuantity = stockQuantity;
this.inStock = stockQuantity != null && stockQuantity > 0;
}
public boolean isInStock() { return inStock; }
}
Step 3.5: Create Controller
Create product-service/src/main/java/com/example/product/controller/ProductController.java:
package com.example.product.controller;
import com.example.product.dto.ProductResponse;
import com.example.product.entity.Product;
import com.example.product.repository.ProductRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductRepository productRepository;
public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@GetMapping
public List<ProductResponse> getAllProducts() {
return productRepository.findAll()
.stream()
.map(ProductResponse::fromEntity)
.toList();
}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> getProductById(@PathVariable Long id) {
return productRepository.findById(id)
.map(product -> ResponseEntity.ok(ProductResponse.fromEntity(product)))
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/sku/{sku}")
public ResponseEntity<ProductResponse> getProductBySku(@PathVariable String sku) {
return productRepository.findBySku(sku)
.map(product -> ResponseEntity.ok(ProductResponse.fromEntity(product)))
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ProductResponse createProduct(@RequestBody Product product) {
Product saved = productRepository.save(product);
return ProductResponse.fromEntity(saved);
}
}
Step 3.6: Create Main Application
Create product-service/src/main/java/com/example/product/ProductServiceApplication.java:
package com.example.product;
import com.example.product.entity.Product;
import com.example.product.repository.ProductRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.math.BigDecimal;
@SpringBootApplication
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
// (#1:Load sample data on startup)
@Bean
CommandLineRunner loadData(ProductRepository repository) {
return args -> {
repository.save(new Product("Laptop", "High-performance laptop", new BigDecimal("999.99"), "LAPTOP-001"));
repository.save(new Product("Mouse", "Wireless mouse", new BigDecimal("29.99"), "MOUSE-001"));
repository.save(new Product("Keyboard", "Mechanical keyboard", new BigDecimal("79.99"), "KEYBOARD-001"));
System.out.println("Sample products loaded!");
};
}
}
Step 3.7: Configure Product Service
Create product-service/src/main/resources/application.yml:
server:
port: 8081 # (#1:Product service runs on 8081)
spring:
application:
name: product-service # (#2:Name used to register with Eureka)
datasource:
url: jdbc:h2:mem:productdb
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
path: /h2-console
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ # (#3:Where to find Eureka Server)
Step 3.8: Test Product Service
Keep Eureka Server running, then in a new terminal:
cd product-catalog/product-service
mvn spring-boot:run
Check Eureka dashboard at http://localhost:8761 - you should see PRODUCT-SERVICE registered!
Test the API:
curl http://localhost:8081/api/products
Part 4: Create Inventory Service
Step 4.1: Create inventory-service/pom.xml
Same dependencies as product-service. Create inventory-service/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>product-catalog</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>inventory-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 4.2: Create Inventory Entity
Create inventory-service/src/main/java/com/example/inventory/entity/Inventory.java:
package com.example.inventory.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "inventory")
public class Inventory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String sku; // (#1:Links to product's SKU)
@Column(nullable = false)
private Integer quantity;
// Constructors
public Inventory() {}
public Inventory(String sku, Integer quantity) {
this.sku = sku;
this.quantity = quantity;
}
// Getters and Setters
public Long getId() { return id; }
public String getSku() { return sku; }
public void setSku(String sku) { this.sku = sku; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
}
Step 4.3: Create Repository and DTO
Create inventory-service/src/main/java/com/example/inventory/repository/InventoryRepository.java:
package com.example.inventory.repository;
import com.example.inventory.entity.Inventory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface InventoryRepository extends JpaRepository<Inventory, Long> {
Optional<Inventory> findBySku(String sku);
}
Create inventory-service/src/main/java/com/example/inventory/dto/InventoryResponse.java:
package com.example.inventory.dto;
public class InventoryResponse {
private String sku;
private Integer quantity;
private boolean inStock;
public InventoryResponse(String sku, Integer quantity) {
this.sku = sku;
this.quantity = quantity;
this.inStock = quantity > 0;
}
public String getSku() { return sku; }
public Integer getQuantity() { return quantity; }
public boolean isInStock() { return inStock; }
}
Step 4.4: Create Controller
Create inventory-service/src/main/java/com/example/inventory/controller/InventoryController.java:
package com.example.inventory.controller;
import com.example.inventory.dto.InventoryResponse;
import com.example.inventory.repository.InventoryRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
private final InventoryRepository inventoryRepository;
public InventoryController(InventoryRepository inventoryRepository) {
this.inventoryRepository = inventoryRepository;
}
// (#1:This endpoint will be called by the API Gateway)
@GetMapping("/{sku}")
public ResponseEntity<InventoryResponse> getInventory(@PathVariable String sku) {
return inventoryRepository.findBySku(sku)
.map(inv -> ResponseEntity.ok(new InventoryResponse(inv.getSku(), inv.getQuantity())))
.orElse(ResponseEntity.ok(new InventoryResponse(sku, 0))); // (#2:Return 0 if not found)
}
}
Step 4.5: Create Main Application
Create inventory-service/src/main/java/com/example/inventory/InventoryServiceApplication.java:
package com.example.inventory;
import com.example.inventory.entity.Inventory;
import com.example.inventory.repository.InventoryRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class InventoryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
@Bean
CommandLineRunner loadData(InventoryRepository repository) {
return args -> {
repository.save(new Inventory("LAPTOP-001", 10));
repository.save(new Inventory("MOUSE-001", 50));
repository.save(new Inventory("KEYBOARD-001", 0)); // (#1:Out of stock!)
System.out.println("Sample inventory loaded!");
};
}
}
Step 4.6: Configure Inventory Service
Create inventory-service/src/main/resources/application.yml:
server:
port: 8082 # (#1:Inventory service runs on 8082)
spring:
application:
name: inventory-service
datasource:
url: jdbc:h2:mem:inventorydb
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
path: /h2-console
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Step 4.7: Test Inventory Service
In a new terminal:
cd product-catalog/inventory-service
mvn spring-boot:run
Check Eureka dashboard - you should now see both PRODUCT-SERVICE and INVENTORY-SERVICE!
curl http://localhost:8082/api/inventory/LAPTOP-001
# Returns: {"sku":"LAPTOP-001","quantity":10,"inStock":true}
Part 5: Create API Gateway
Instead of clients knowing about multiple services (product at :8081, inventory at :8082), they only talk to the Gateway at :8080. The Gateway routes requests to the right service.
Benefits: Single entry point, can add authentication, rate limiting, logging in one place.
Step 5.1: Create api-gateway/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>product-catalog</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>api-gateway</artifactId>
<dependencies>
<!-- (#1:Spring Cloud Gateway) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- (#2:Eureka Client to discover services) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- (#3:Circuit Breaker for resilience) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 5.2: Create Main Application
Create api-gateway/src/main/java/com/example/gateway/ApiGatewayApplication.java:
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
Step 5.3: Configure Gateway Routes
Create api-gateway/src/main/resources/application.yml:
server:
port: 8080 # (#1:Gateway is the main entry point)
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # (#2:Enable service discovery routing)
lower-case-service-id: true
routes:
# (#3:Route /products/** to product-service)
- id: product-service
uri: lb://product-service # (#4:lb = load balanced via Eureka)
predicates:
- Path=/api/products/**
# (#5:Route /inventory/** to inventory-service)
- id: inventory-service
uri: lb://inventory-service
predicates:
- Path=/api/inventory/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# (#6:Enable actuator endpoints for monitoring)
management:
endpoints:
web:
exposure:
include: health,info,gateway
uri: lb://product-service- "lb" means load-balanced; Eureka provides the actual URLpredicates- Conditions that must match for routingPath=/api/products/**- All requests starting with /api/products go to product-service
Step 5.4: Test the Gateway
Start the gateway (keep other services running):
cd product-catalog/api-gateway
mvn spring-boot:run
Now test through the gateway (port 8080, not 8081):
# Get products through gateway
curl http://localhost:8080/api/products
# Get inventory through gateway
curl http://localhost:8080/api/inventory/LAPTOP-001
All requests go through port 8080, but are routed to the correct service!
Part 6: Add Circuit Breaker (Resilience)
Like an electrical circuit breaker in your home. If there's a problem (service is down), the breaker "trips" and stops sending requests. This prevents cascading failures.
States:
- CLOSED: Normal operation, requests flow through
- OPEN: Service is failing, return fallback immediately
- HALF-OPEN: Test if service recovered
Step 6.1: Add Fallback Controller
Create api-gateway/src/main/java/com/example/gateway/controller/FallbackController.java:
package com.example.gateway.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/fallback")
public class FallbackController {
// (#1:Called when product-service is down)
@GetMapping("/products")
public ResponseEntity<Map<String, String>> productServiceFallback() {
return ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(Map.of(
"message", "Product service is currently unavailable",
"status", "SERVICE_DOWN"
));
}
// (#2:Called when inventory-service is down)
@GetMapping("/inventory")
public ResponseEntity<Map<String, String>> inventoryServiceFallback() {
return ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(Map.of(
"message", "Inventory service is currently unavailable",
"status", "SERVICE_DOWN"
));
}
}
Step 6.2: Update Gateway Configuration
Update api-gateway/src/main/resources/application.yml to add circuit breaker:
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
# (#1:Add circuit breaker filter)
- name: CircuitBreaker
args:
name: productCircuitBreaker
fallbackUri: forward:/fallback/products
- id: inventory-service
uri: lb://inventory-service
predicates:
- Path=/api/inventory/**
filters:
- name: CircuitBreaker
args:
name: inventoryCircuitBreaker
fallbackUri: forward:/fallback/inventory
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# (#2:Circuit breaker configuration)
resilience4j:
circuitbreaker:
instances:
productCircuitBreaker:
slidingWindowSize: 10 # (#3:Number of calls to track)
failureRateThreshold: 50 # (#4:Open circuit if 50% fail)
waitDurationInOpenState: 10000 # (#5:Wait 10s before trying again)
inventoryCircuitBreaker:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000
management:
endpoints:
web:
exposure:
include: health,info,gateway
Step 6.3: Test Circuit Breaker
1. Stop the product-service (Ctrl+C in its terminal)
2. Try to access products through gateway:
curl http://localhost:8080/api/products
Expected response (fallback):
{
"message": "Product service is currently unavailable",
"status": "SERVICE_DOWN"
}
3. Restart product-service and try again - it works!
Deliverables Checklist
- Eureka Server runs on port 8761
- Product Service registers with Eureka (port 8081)
- Inventory Service registers with Eureka (port 8082)
- API Gateway routes requests (port 8080)
- Gateway routes /api/products/** to product-service
- Gateway routes /api/inventory/** to inventory-service
- Circuit breaker returns fallback when service is down
- All 4 services visible in Eureka dashboard
Running All Services
You need 4 terminal windows. Start in this order:
# Terminal 1: Eureka Server (start first, wait for it to be ready)
cd product-catalog/eureka-server && mvn spring-boot:run
# Terminal 2: Product Service
cd product-catalog/product-service && mvn spring-boot:run
# Terminal 3: Inventory Service
cd product-catalog/inventory-service && mvn spring-boot:run
# Terminal 4: API Gateway
cd product-catalog/api-gateway && mvn spring-boot:run
Bonus Challenges
Create an endpoint in the gateway that combines product and inventory data. GET /api/products-with-stock should return products with their stock quantities.
Create a docker-compose.yml that runs all 4 services with a single command.
Create an order-service that creates orders, checking inventory before confirming.