Building Microservices with Spring Boot
A comprehensive guide to building microservices architecture using Spring Boot, Spring Cloud, and best practices
Building Microservices with Spring Boot
This guide demonstrates how to build a microservices architecture using Spring Boot and Spring Cloud, including service discovery, API gateway, circuit breakers, and distributed tracing.
Video Tutorial
Learn more about Spring Boot microservices in this comprehensive video tutorial:
Prerequisites
<properties>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
</dependencies>
<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>
Service Discovery with Eureka
Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryApplication.class, args);
}
}
# application.yml
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
Microservice Registration
@SpringBootApplication
@EnableDiscoveryClient
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
# application.yml
spring:
application:
name: product-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
API Gateway
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
# application.yml
spring:
cloud:
gateway:
routes:
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- name: CircuitBreaker
args:
name: productService
fallbackUri: forward:/fallback/products
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- name: CircuitBreaker
args:
name: orderService
fallbackUri: forward:/fallback/orders
Circuit Breaker with Resilience4j
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return ResponseEntity.ok(productService.getProduct(id));
}
public ResponseEntity<Product> getProductFallback(Long id, Exception ex) {
return ResponseEntity.ok(new Product(id, "Fallback Product", BigDecimal.ZERO));
}
}
# application.yml
resilience4j:
circuitbreaker:
instances:
productService:
slidingWindowSize: 10
minimumNumberOfCalls: 5
failureRateThreshold: 50
waitDurationInOpenState: 5000
permittedNumberOfCallsInHalfOpenState: 3
Distributed Tracing with Sleuth and Zipkin
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
log.info("Creating order for user: {}", request.getUserId());
Order order = orderService.createOrder(request);
log.info("Order created with ID: {}", order.getId());
return ResponseEntity.ok(order);
}
}
# application.yml
spring:
sleuth:
sampler:
probability: 1.0
zipkin:
baseUrl: http://localhost:9411
Inter-Service Communication with Feign
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/api/products/{id}")
Product getProduct(@PathVariable("id") Long id);
@GetMapping("/api/products")
List<Product> getAllProducts();
}
@Service
public class OrderService {
private final ProductClient productClient;
public Order createOrder(OrderRequest request) {
Product product = productClient.getProduct(request.getProductId());
// Create order logic
return order;
}
}
Event-Driven Architecture with Spring Cloud Stream
@Configuration
public class StreamConfig {
@Bean
public Function<OrderCreatedEvent, OrderProcessedEvent> processOrder() {
return order -> {
// Process order logic
return new OrderProcessedEvent(order.getId());
};
}
}
# application.yml
spring:
cloud:
stream:
bindings:
processOrder-in-0:
destination: order-created
processOrder-out-0:
destination: order-processed
Configuration Management with Spring Cloud Config
Config Server
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# application.yml
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-repo/config
searchPaths: config
Microservice Configuration
# bootstrap.yml
spring:
cloud:
config:
uri: http://localhost:8888
application:
name: product-service
profiles:
active: dev
Monitoring with Spring Boot Admin
@SpringBootApplication
@EnableAdminServer
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class, args);
}
}
# application.yml
spring:
boot:
admin:
client:
url: http://localhost:8080
management:
endpoints:
web:
exposure:
include: "*"
Testing Microservices
@SpringBootTest
@AutoConfigureWireMock(port = 0)
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void whenCreateOrder_thenSuccess() {
// Setup WireMock stubs
stubFor(get(urlEqualTo("/api/products/1"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"id\":1,\"name\":\"Test Product\",\"price\":99.99}")));
OrderRequest request = new OrderRequest(1L, 1L);
Order order = orderService.createOrder(request);
assertNotNull(order);
assertEquals(1L, order.getProductId());
}
}
Deployment Considerations
- Docker Containerization
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
- Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 3
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product-service
image: product-service:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
Best Practices
-
Service Design
- Keep services small and focused
- Use domain-driven design principles
- Implement proper error handling
- Use asynchronous communication when possible
-
Data Management
- Each service should own its data
- Use eventual consistency
- Implement database per service pattern
- Handle distributed transactions carefully
-
Security
- Implement OAuth2/JWT authentication
- Use HTTPS everywhere
- Implement rate limiting
- Follow the principle of least privilege
-
Monitoring
- Implement comprehensive logging
- Use distributed tracing
- Monitor service health
- Set up alerts for critical issues
-
Performance
- Use caching strategically
- Implement proper timeout policies
- Use connection pooling
- Optimize database queries
Common Patterns
- API Composition
@Service
public class OrderDetailsService {
private final OrderService orderService;
private final ProductService productService;
private final UserService userService;
public OrderDetails getOrderDetails(Long orderId) {
Order order = orderService.getOrder(orderId);
Product product = productService.getProduct(order.getProductId());
User user = userService.getUser(order.getUserId());
return new OrderDetails(order, product, user);
}
}
- Saga Pattern
@Service
public class OrderSaga {
@Transactional
public void createOrder(OrderRequest request) {
try {
// Create order
Order order = orderService.createOrder(request);
// Reserve inventory
inventoryService.reserve(order.getProductId(), order.getQuantity());
// Process payment
paymentService.process(order.getId(), order.getTotal());
} catch (Exception e) {
// Compensating transactions
compensate(request);
throw e;
}
}
}
Conclusion
Building microservices with Spring Boot requires careful consideration of:
- Service discovery and registration
- API gateway implementation
- Circuit breaker patterns
- Distributed tracing
- Event-driven architecture
- Configuration management
- Monitoring and logging
- Testing strategies
- Deployment considerations
For more Spring Boot topics, check out: