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

  1. Docker Containerization
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
  1. 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

  1. Service Design

    • Keep services small and focused
    • Use domain-driven design principles
    • Implement proper error handling
    • Use asynchronous communication when possible
  2. Data Management

    • Each service should own its data
    • Use eventual consistency
    • Implement database per service pattern
    • Handle distributed transactions carefully
  3. Security

    • Implement OAuth2/JWT authentication
    • Use HTTPS everywhere
    • Implement rate limiting
    • Follow the principle of least privilege
  4. Monitoring

    • Implement comprehensive logging
    • Use distributed tracing
    • Monitor service health
    • Set up alerts for critical issues
  5. Performance

    • Use caching strategically
    • Implement proper timeout policies
    • Use connection pooling
    • Optimize database queries

Common Patterns

  1. 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);
    }
}
  1. 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: