google-site-verification: google61fe8ba583a51912.html
Microservices with Spring Boot

Microservices architecture involves building applications as a collection of small, independent services that communicate over well-defined APIs. Here's a comprehensive guide to building microservices with Spring Boot.

1. Microservice Project Structure

Typical Microservices Architecture

microservices-project/ ├── service-registry/ # Eureka Server ├── api-gateway/ # Spring Cloud Gateway ├── config-server/ # Spring Cloud Config ├── user-service/ # Microservice 1 ├── order-service/ # Microservice 2 ├── product-service/ # Microservice 3 └── notification-service/ # Microservice 4

2. Service Registry with Eureka

Eureka Server Configuration

// service-registry/pom.xml <?xml version="1.0" encoding="UTF-8"?> <project> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project> // service-registry/src/main/java/com/example/ServiceRegistryApplication.java package com.example.serviceregistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class ServiceRegistryApplication { public static void main(String[] args) { SpringApplication.run(ServiceRegistryApplication.class, args); } } // service-registry/src/main/resources/application.yml server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false server: enable-self-preservation: false eviction-interval-timer-in-ms: 5000 spring: application: name: service-registry

3. API Gateway with Spring Cloud Gateway

Gateway Configuration

// api-gateway/pom.xml <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </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> // api-gateway/src/main/java/com/example/ApiGatewayApplication.java package com.example.apigateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } } // api-gateway/src/main/resources/application.yml server: port: 8080 spring: application: name: api-gateway cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: user-service uri: lb://user-service predicates: - Path=/api/users/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 - name: CircuitBreaker args: name: userService fallbackUri: forward:/fallback/user-service - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** filters: - StripPrefix=1 - id: product-service uri: lb://product-service predicates: - Path=/api/products/** eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: prefer-ip-address: true management: endpoints: web: exposure: include: health,info,gateway // Custom Gateway Filters package com.example.apigateway.filters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class LoggingFilter implements GlobalFilter, Ordered { private final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { logger.info("Request: {} {}", exchange.getRequest().getMethod(), exchange.getRequest().getPath()); // Add correlation ID to headers String correlationId = java.util.UUID.randomUUID().toString(); exchange.getRequest().mutate() .header("X-Correlation-ID", correlationId) .build(); return chain.filter(exchange).then(Mono.fromRunnable(() -> { logger.info("Response status: {}", exchange.getResponse().getStatusCode()); })); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } } // Fallback Controller package com.example.apigateway.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.Map; @RestController public class FallbackController { @GetMapping("/fallback/user-service") public ResponseEntity<Map<String, String>> userServiceFallback() { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(Collections.singletonMap("message", "User service is temporarily unavailable. Please try again later.")); } }

4. User Service Microservice

Complete User Service Implementation

// user-service/pom.xml <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>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> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies> // user-service/src/main/java/com/example/userservice/UserServiceApplication.java package com.example.userservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } } // Entity package com.example.userservice.entity; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import java.time.LocalDateTime; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Name is required") private String name; @Email(message = "Email should be valid") @NotBlank(message = "Email is required") @Column(unique = true) private String email; private String phone; @Enumerated(EnumType.STRING) private UserStatus status = UserStatus.ACTIVE; private LocalDateTime createdAt; private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } // Constructors, Getters, Setters public User() {} public User(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } // 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 getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public UserStatus getStatus() { return status; } public void setStatus(UserStatus status) { this.status = status; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } } enum UserStatus { ACTIVE, INACTIVE, SUSPENDED } // Repository package com.example.userservice.repository; import com.example.userservice.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); boolean existsByEmail(String email); } // DTOs package com.example.userservice.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; public class UserRequest { @NotBlank(message = "Name is required") private String name; @Email(message = "Email should be valid") @NotBlank(message = "Email is required") private String email; private String phone; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } } package com.example.userservice.dto; import java.time.LocalDateTime; public class UserResponse { private Long id; private String name; private String email; private String phone; private String status; private LocalDateTime createdAt; // Constructors public UserResponse() {} public UserResponse(Long id, String name, String email, String phone, String status, LocalDateTime createdAt) { this.id = id; this.name = name; this.email = email; this.phone = phone; this.status = status; this.createdAt = createdAt; } // 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 getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } } // Service Layer package com.example.userservice.service; import com.example.userservice.dto.UserRequest; import com.example.userservice.dto.UserResponse; import com.example.userservice.entity.User; import com.example.userservice.entity.UserStatus; import com.example.userservice.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Service @Transactional public class UserService { @Autowired private UserRepository userRepository; public UserResponse createUser(UserRequest userRequest) { if (userRepository.existsByEmail(userRequest.getEmail())) { throw new RuntimeException("User with email " + userRequest.getEmail() + " already exists"); } User user = new User(userRequest.getName(), userRequest.getEmail(), userRequest.getPhone()); User savedUser = userRepository.save(user); return mapToUserResponse(savedUser); } @Transactional(readOnly = true) public List<UserResponse> getAllUsers() { return userRepository.findAll().stream() .map(this::mapToUserResponse) .collect(Collectors.toList()); } @Transactional(readOnly = true) public UserResponse getUserById(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found with id: " + id)); return mapToUserResponse(user); } public UserResponse updateUser(Long id, UserRequest userRequest) { User user = userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found with id: " + id)); user.setName(userRequest.getName()); user.setPhone(userRequest.getPhone()); User updatedUser = userRepository.save(user); return mapToUserResponse(updatedUser); } public void deleteUser(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found with id: " + id)); user.setStatus(UserStatus.INACTIVE); userRepository.save(user); } private UserResponse mapToUserResponse(User user) { return new UserResponse( user.getId(), user.getName(), user.getEmail(), user.getPhone(), user.getStatus().name(), user.getCreatedAt() ); } } // Controller package com.example.userservice.controller; import com.example.userservice.dto.UserRequest; import com.example.userservice.dto.UserResponse; import com.example.userservice.service.UserService; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping public ResponseEntity<UserResponse> createUser(@Valid @RequestBody UserRequest userRequest) { UserResponse userResponse = userService.createUser(userRequest); return ResponseEntity.status(HttpStatus.CREATED).body(userResponse); } @GetMapping public ResponseEntity<List<UserResponse>> getAllUsers() { List<UserResponse> users = userService.getAllUsers(); return ResponseEntity.ok(users); } @GetMapping("/{id}") public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) { UserResponse user = userService.getUserById(id); return ResponseEntity.ok(user); } @PutMapping("/{id}") public ResponseEntity<UserResponse> updateUser( @PathVariable Long id, @Valid @RequestBody UserRequest userRequest) { UserResponse userResponse = userService.updateUser(id, userRequest); return ResponseEntity.ok(userResponse); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } } // Global Exception Handler package com.example.userservice.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) { ErrorResponse errorResponse = new ErrorResponse( HttpStatus.BAD_REQUEST.value(), ex.getMessage(), LocalDateTime.now() ); return ResponseEntity.badRequest().body(errorResponse); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); return ResponseEntity.badRequest().body(errors); } } class ErrorResponse { private int status; private String message; private LocalDateTime timestamp; public ErrorResponse(int status, String message, LocalDateTime timestamp) { this.status = status; this.message = message; this.timestamp = timestamp; } // Getters and setters public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public LocalDateTime getTimestamp() { return timestamp; } public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } } // user-service/src/main/resources/application.yml server: port: 8081 spring: application: name: user-service datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: create-drop show-sql: true h2: console: enabled: true eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: prefer-ip-address: true management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always logging: level: com.example.userservice: DEBUG

5. Order Service with Feign Client

Order Service Implementation

// order-service/src/main/java/com/example/orderservice/OrderServiceApplication.java package com.example.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } // Feign Client for User Service package com.example.orderservice.client; import com.example.orderservice.dto.UserResponse; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "user-service", fallback = UserServiceFallback.class) public interface UserServiceClient { @GetMapping("/api/users/{userId}") UserResponse getUserById(@PathVariable("userId") Long userId); } // Fallback Implementation package com.example.orderservice.client; import com.example.orderservice.dto.UserResponse; import org.springframework.stereotype.Component; @Component public class UserServiceFallback implements UserServiceClient { @Override public UserResponse getUserById(Long userId) { // Return a default user when user service is unavailable return new UserResponse(userId, "Unknown User", "[email protected]", "N/A", "UNAVAILABLE", null); } } // Order Entity package com.example.orderservice.entity; import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long userId; private BigDecimal totalAmount; @Enumerated(EnumType.STRING) private OrderStatus status = OrderStatus.PENDING; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "order") private List<OrderItem> orderItems = new ArrayList<>(); private LocalDateTime createdAt; private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } // Helper methods public void addOrderItem(OrderItem item) { orderItems.add(item); item.setOrder(this); } // Constructors, Getters, Setters public Order() {} public Order(Long userId) { this.userId = userId; this.totalAmount = BigDecimal.ZERO; } // Getters and setters... public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public BigDecimal getTotalAmount() { return totalAmount; } public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; } public OrderStatus getStatus() { return status; } public void setStatus(OrderStatus status) { this.status = status; } public List<OrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } } @Entity @Table(name = "order_items") class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long productId; private Integer quantity; private BigDecimal price; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") private Order order; // Constructors, Getters, Setters public OrderItem() {} public OrderItem(Long productId, Integer quantity, BigDecimal price) { this.productId = productId; this.quantity = quantity; this.price = price; } // Getters and setters... public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProductId() { return productId; } public void setProductId(Long productId) { this.productId = productId; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } } enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED } // DTOs package com.example.orderservice.dto; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; public class OrderRequest { private Long userId; private List<OrderItemRequest> items; // Getters and setters public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public List<OrderItemRequest> getItems() { return items; } public void setItems(List<OrderItemRequest> items) { this.items = items; } } class OrderItemRequest { private Long productId; private Integer quantity; private BigDecimal price; // Getters and setters public Long getProductId() { return productId; } public void setProductId(Long productId) { this.productId = productId; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } } public class OrderResponse { private Long id; private Long userId; private UserResponse user; private BigDecimal totalAmount; private String status; private List<OrderItemResponse> items; private LocalDateTime createdAt; // Constructors, Getters, Setters public OrderResponse() {} // Getters and setters... public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public UserResponse getUser() { return user; } public void setUser(UserResponse user) { this.user = user; } public BigDecimal getTotalAmount() { return totalAmount; } public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public List<OrderItemResponse> getItems() { return items; } public void setItems(List<OrderItemResponse> items) { this.items = items; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } } class OrderItemResponse { private Long productId; private Integer quantity; private BigDecimal price; // Getters and setters public Long getProductId() { return productId; } public void setProductId(Long productId) { this.productId = productId; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } } // Service package com.example.orderservice.service; import com.example.orderservice.client.UserServiceClient; import com.example.orderservice.dto.*; import com.example.orderservice.entity.Order; import com.example.orderservice.entity.OrderItem; import com.example.orderservice.entity.OrderStatus; import com.example.orderservice.repository.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; @Service @Transactional public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private UserServiceClient userServiceClient; public OrderResponse createOrder(OrderRequest orderRequest) { // Validate user exists (using Feign client) UserResponse user = userServiceClient.getUserById(orderRequest.getUserId()); Order order = new Order(orderRequest.getUserId()); // Add order items and calculate total BigDecimal totalAmount = BigDecimal.ZERO; for (OrderItemRequest itemRequest : orderRequest.getItems()) { OrderItem orderItem = new OrderItem( itemRequest.getProductId(), itemRequest.getQuantity(), itemRequest.getPrice() ); order.addOrderItem(orderItem); totalAmount = totalAmount.add(itemRequest.getPrice().multiply( BigDecimal.valueOf(itemRequest.getQuantity()))); } order.setTotalAmount(totalAmount); Order savedOrder = orderRepository.save(order); return mapToOrderResponse(savedOrder, user); } @Transactional(readOnly = true) public List<OrderResponse> getAllOrders() { return orderRepository.findAll().stream() .map(order -> { UserResponse user = userServiceClient.getUserById(order.getUserId()); return mapToOrderResponse(order, user); }) .collect(Collectors.toList()); } @Transactional(readOnly = true) public OrderResponse getOrderById(Long id) { Order order = orderRepository.findById(id) .orElseThrow(() -> new RuntimeException("Order not found with id: " + id)); UserResponse user = userServiceClient.getUserById(order.getUserId()); return mapToOrderResponse(order, user); } public OrderResponse updateOrderStatus(Long id, OrderStatus status) { Order order = orderRepository.findById(id) .orElseThrow(() -> new RuntimeException("Order not found with id: " + id)); order.setStatus(status); Order updatedOrder = orderRepository.save(order); UserResponse user = userServiceClient.getUserById(updatedOrder.getUserId()); return mapToOrderResponse(updatedOrder, user); } private OrderResponse mapToOrderResponse(Order order, UserResponse user) { OrderResponse response = new OrderResponse(); response.setId(order.getId()); response.setUserId(order.getUserId()); response.setUser(user); response.setTotalAmount(order.getTotalAmount()); response.setStatus(order.getStatus().name()); response.setCreatedAt(order.getCreatedAt()); response.setItems(order.getOrderItems().stream() .map(item -> { OrderItemResponse itemResponse = new OrderItemResponse(); itemResponse.setProductId(item.getProductId()); itemResponse.setQuantity(item.getQuantity()); itemResponse.setPrice(item.getPrice()); return itemResponse; }) .collect(Collectors.toList())); return response; } } // Controller package com.example.orderservice.controller; import com.example.orderservice.dto.OrderRequest; import com.example.orderservice.dto.OrderResponse; import com.example.orderservice.entity.OrderStatus; import com.example.orderservice.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderService orderService; @PostMapping public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest orderRequest) { OrderResponse orderResponse = orderService.createOrder(orderRequest); return ResponseEntity.status(HttpStatus.CREATED).body(orderResponse); } @GetMapping public ResponseEntity<List<OrderResponse>> getAllOrders() { List<OrderResponse> orders = orderService.getAllOrders(); return ResponseEntity.ok(orders); } @GetMapping("/{id}") public ResponseEntity<OrderResponse> getOrderById(@PathVariable Long id) { OrderResponse order = orderService.getOrderById(id); return ResponseEntity.ok(order); } @PatchMapping("/{id}/status") public ResponseEntity<OrderResponse> updateOrderStatus( @PathVariable Long id, @RequestParam OrderStatus status) { OrderResponse order = orderService.updateOrderStatus(id, status); return ResponseEntity.ok(order); } }

6. Configuration with Spring Cloud Config

Config Server Setup

// config-server/pom.xml <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> // config-server/src/main/java/com/example/ConfigServerApplication.java package com.example.configserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } } // config-server/src/main/resources/application.yml server: port: 8888 spring: application: name: config-server cloud: config: server: git: uri: https://github.com/your-repo/config-repo default-label: main clone-on-start: true // Client-side configuration (user-service/src/main/resources/bootstrap.yml) spring: application: name: user-service cloud: config: uri: http://localhost:8888 fail-fast: true

7. Distributed Tracing with Sleuth and Zipkin

Adding Distributed Tracing

// Add to each microservice's pom.xml <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> // application.yml configuration spring: sleuth: sampler: probability: 1.0 # 100% sampling for development zipkin: base-url: http://localhost:9411 // Custom trace interceptor package com.example.userservice.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.sleuth.Tracer; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Component public class TraceInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(TraceInterceptor.class); private final Tracer tracer; public TraceInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String traceId = tracer.currentSpan().context().traceId(); String spanId = tracer.currentSpan().context().spanId(); logger.info("Incoming request - TraceId: {}, SpanId: {}, Path: {}", traceId, spanId, request.getRequestURI()); // Add trace ID to response headers response.addHeader("X-Trace-Id", traceId); return true; } }

8. Resilience Patterns with Resilience4j

Circuit Breaker and Retry Patterns

// Add to pom.xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> // Configuration package com.example.orderservice.config; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.timelimiter.TimeLimiterConfig; import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory; import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; @Configuration public class ResilienceConfig { @Bean public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .timeLimiterConfig(TimeLimiterConfig.custom() .timeoutDuration(Duration.ofSeconds(5)) .build()) .circuitBreakerConfig(CircuitBreakerConfig.custom() .slidingWindowSize(10) .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(10)) .build()) .build()); } } // Using Circuit Breaker in Service package com.example.orderservice.service; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ProductService { @Autowired private ProductServiceClient productServiceClient; @CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback") @Retry(name = "productService") public ProductResponse getProduct(Long productId) { return productServiceClient.getProductById(productId); } private ProductResponse getProductFallback(Long productId, Exception ex) { // Fallback logic return new ProductResponse(productId, "Fallback Product", BigDecimal.ZERO, "UNAVAILABLE"); } }

9. Docker Configuration

Dockerfile for Each Microservice

# user-service/Dockerfile FROM openjdk:17-jdk-slim VOLUME /tmp COPY target/user-service-1.0.0.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] EXPOSE 8081

Docker Compose

# docker-compose.yml version: '3.8' services: service-registry: build: ./service-registry ports: - "8761:8761" networks: - microservices-network api-gateway: build: ./api-gateway ports: - "8080:8080" depends_on: - service-registry networks: - microservices-network user-service: build: ./user-service ports: - "8081:8081" depends_on: - service-registry networks: - microservices-network order-service: build: ./order-service ports: - "8082:8082" depends_on: - service-registry networks: - microservices-network networks: microservices-network: driver: bridge

Key Microservices Patterns with Spring Boot

  1. Service Discovery - Eureka for service registration and discovery
  2. API Gateway - Single entry point with routing, filtering, and security
  3. Configuration Management - Centralized configuration with Spring Cloud Config
  4. Inter-Service Communication - Feign clients for REST calls
  5. Circuit Breaker - Resilience4j for fault tolerance
  6. Distributed Tracing - Sleuth and Zipkin for request tracking
  7. Centralized Logging - ELK stack or similar for log aggregation
  8. Containerization - Docker for consistent deployment

This architecture provides scalability, resilience, and maintainability for modern cloud-native applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper