From 0dc53558b40bdb45c10d369c4850a2de3bf9fa74 Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey Date: Thu, 2 Oct 2025 13:29:09 +0530 Subject: [PATCH 1/6] Add Microservices Bulkhead pattern implementation - Implements resource isolation with dedicated thread pools - Demonstrates fail-fast behavior under load - Fixes #3228 --- microservices-bulkhead/README.md | 192 ++++++++++++++++++ microservices-bulkhead/pom.xml | 144 +++++++++++++ .../main/java/com/iluwatar/bulkhead/App.java | 137 +++++++++++++ .../iluwatar/bulkhead/BackgroundService.java | 38 ++++ .../iluwatar/bulkhead/BulkheadService.java | 143 +++++++++++++ .../main/java/com/iluwatar/bulkhead/Task.java | 60 ++++++ .../java/com/iluwatar/bulkhead/TaskType.java | 21 ++ .../com/iluwatar/bulkhead/UserService.java | 38 ++++ 8 files changed, 773 insertions(+) create mode 100644 microservices-bulkhead/README.md create mode 100644 microservices-bulkhead/pom.xml create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java create mode 100644 microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java diff --git a/microservices-bulkhead/README.md b/microservices-bulkhead/README.md new file mode 100644 index 000000000000..be7851c77598 --- /dev/null +++ b/microservices-bulkhead/README.md @@ -0,0 +1,192 @@ +--- +title: "Bulkhead Pattern in Java: Isolating Resources for Resilient Microservices" +shortTitle: Bulkhead +description: "Learn how the Bulkhead pattern in Java isolates critical system resources to prevent cascade failures in microservices. Includes real-world examples, code demonstrations, and best practices for building resilient distributed systems." +category: Resilience +language: en +tag: + - Resilience + - Fault tolerance + - Microservices + - Performance + - Scalability + - Thread management +--- + +## Also known as + +* Resource Isolation Pattern +* Partition Pattern + +## Intent of Bulkhead Design Pattern + +The Bulkhead pattern isolates critical system resources for each service or component to prevent failures or heavy load in one part of the system from cascading and degrading the entire application. By partitioning resources—often via separate thread pools or connection pools—the system ensures other services remain operational even if one service becomes overloaded or fails. + +## Detailed Explanation of Bulkhead Pattern with Real-World Examples + +Real-world example + +> Consider a modern cruise ship with multiple watertight compartments (bulkheads). If one compartment is breached and starts flooding, the bulkheads prevent water from spreading to other compartments, keeping the ship afloat. Similarly, in software systems, the Bulkhead pattern creates isolated resource pools for different services. If one service experiences issues (like a slow external API or heavy load), it only affects its dedicated resources, while other services continue operating normally with their own resource pools. + +In plain words + +> The Bulkhead pattern partitions system resources into isolated pools so that failures in one area don't consume all available resources and bring down the entire system. + +## Programmatic Example of Bulkhead Pattern in Java + +The Bulkhead pattern implementation demonstrates resource isolation using dedicated thread pools for different services. Here we have a `UserService` handling critical user requests and a `BackgroundService` handling non-critical tasks. + +First, let's look at the base `BulkheadService` class that provides resource isolation: + +``` +public abstract class BulkheadService { +private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); + +protected final ThreadPoolExecutor executor; +protected final String serviceName; + +protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { +this.serviceName = serviceName; + + // Create thread pool with bounded queue for resource isolation + this.executor = new ThreadPoolExecutor( + maxPoolSize, + maxPoolSize, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(queueCapacity), + new ThreadPoolExecutor.AbortPolicy() // fail-fast when full + ); + + LOGGER.info("Created {} with {} threads and queue capacity {}", + serviceName, maxPoolSize, queueCapacity); +} + +public boolean submitTask(Task task) { +try { +executor.execute(() -> processTask(task)); +LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); +return true; +} catch (RejectedExecutionException e) { +LOGGER.warn("[{}] Task '{}' REJECTED - bulkhead is full", serviceName, task.getName()); +handleRejectedTask(task); +return false; +} +} +} +``` + +The `UserService` handles critical user-facing requests with dedicated resources: + +``` +public class UserService extends BulkheadService { +private static final int DEFAULT_QUEUE_CAPACITY = 10; + +public UserService(int maxThreads) { +super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); +} +} +``` + +The `BackgroundService` handles non-critical background tasks with its own isolated resources: + +``` +public class BackgroundService extends BulkheadService { +private static final int DEFAULT_QUEUE_CAPACITY = 20; + +public BackgroundService(int maxThreads) { +super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); +} +} +``` + +Here's the demonstration showing how the Bulkhead pattern prevents cascade failures: + +``` +public class App { +public static void main(String[] args) { +BulkheadService userService = new UserService(3); +BulkheadService backgroundService = new BackgroundService(2); + + // Overload background service with many tasks + for (int i = 1; i <= 10; i++) { + Task task = new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000); + backgroundService.submitTask(task); + } + + // User service remains responsive despite background service overload + for (int i = 1; i <= 3; i++) { + Task task = new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300); + boolean accepted = userService.submitTask(task); + LOGGER.info("User request {} accepted: {}", i, accepted); + } +} +} +``` + +Program output: + +``` +[BackgroundService] Task 'Heavy-Background-Job-1' submitted successfully +[BackgroundService] Task 'Heavy-Background-Job-2' submitted successfully +[BackgroundService] Task 'Heavy-Background-Job-3' REJECTED - bulkhead is full +... +[UserService] Task 'Critical-User-Request-1' submitted successfully +[UserService] Task 'Critical-User-Request-2' submitted successfully +[UserService] Task 'Critical-User-Request-3' submitted successfully +User request 1 accepted: true +User request 2 accepted: true +User request 3 accepted: true +``` + +The output demonstrates that even when the background service is overloaded and rejecting tasks, the user service continues to accept and process requests successfully due to resource isolation. + +## When to Use the Bulkhead Pattern in Java + +* When building microservices architectures where service failures should not cascade +* When different operations have varying criticality levels (e.g., user-facing vs. background tasks) +* When external dependencies (databases, APIs) might become slow or unavailable +* When you need to guarantee minimum service levels for critical operations +* In high-throughput systems where resource exhaustion in one area could impact other services + +## Real-World Applications of Bulkhead Pattern in Java + +* Netflix's Hystrix library implements bulkheads using thread pool isolation +* Resilience4j provides bulkhead implementations for Java applications +* AWS Lambda functions run in isolated execution environments (bulkheads) +* Kubernetes resource limits and quotas implement bulkhead principles +* Database connection pools per service in microservices architectures + +## Benefits and Trade-offs of Bulkhead Pattern + +Benefits: + +* Prevents cascade failures across services +* Improves system resilience and availability +* Provides predictable degradation under load +* Enables independent scaling of different services +* Facilitates easier capacity planning and monitoring + +Trade-offs: + +* Increased resource overhead (multiple thread pools) +* More complex configuration and tuning +* Potential for resource underutilization if pools are too large +* Requires careful capacity planning for each bulkhead +* May increase overall latency due to queuing + +## Related Java Design Patterns + +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Often used together with Bulkhead; Circuit Breaker stops calling failing services while Bulkhead limits resources +* [Retry](https://java-design-patterns.com/patterns/retry/): Can be combined with Bulkhead for transient failure handling +* [Throttling](https://java-design-patterns.com/patterns/throttling/): Similar goal of resource management but focuses on rate limiting rather than isolation +* [Load Balancer](https://java-design-patterns.com/patterns/load-balancer/): Works at request distribution level while Bulkhead works at resource isolation level + +## References and Credits + +* [Release It!: Design and Deploy Production-Ready Software](https://amzn.to/3Uul4kF) - Michael T. Nygard +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) - Chris Richardson +* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/3RYRz96) - Sam Newman +* [Resilience4j Documentation - Bulkhead](https://resilience4j.readme.io/docs/bulkhead) +* [Microsoft Azure Architecture - Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) +* [Microservices.io - Bulkhead Pattern](https://microservices.io/patterns/reliability/bulkhead.html) \ No newline at end of file diff --git a/microservices-bulkhead/pom.xml b/microservices-bulkhead/pom.xml new file mode 100644 index 000000000000..0d11453a0527 --- /dev/null +++ b/microservices-bulkhead/pom.xml @@ -0,0 +1,144 @@ + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + bulkhead + jar + + Bulkhead + + The Bulkhead pattern isolates critical system resources for each service or component + to prevent failures or heavy load in one part from cascading and degrading the entire system. + By partitioning resources via separate thread pools, the system ensures other services + remain operational even if one service becomes overloaded or fails. + + + + UTF-8 + + + + + + org.slf4j + slf4j-api + + + + + ch.qos.logback + logback-classic + runtime + + + + + org.projectlombok + lombok + provided + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.junit.jupiter + junit-jupiter-params + test + + + + + org.mockito + mockito-core + test + + + + + org.assertj + assertj-core + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + + org.apache.maven.plugins + maven-pmd-plugin + + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + + diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java new file mode 100644 index 000000000000..194a52f8e819 --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java @@ -0,0 +1,137 @@ +package com.iluwatar.bulkhead; + +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Bulkhead pattern isolates critical system resources for each service or component + * to prevent failures or heavy load in one part from cascading and degrading the entire system. + * + *

In this example, we demonstrate resource isolation using separate thread pools for + * different services. The {@link UserService} handles user-facing requests while + * {@link BackgroundService} handles background tasks. Each service has its own dedicated + * thread pool (bulkhead), ensuring that if one service becomes overloaded, the other + * continues to function normally. + * + *

Key concepts demonstrated: + *

+ */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line arguments + */ + public static void main(String[] args) { + LOGGER.info("Starting Bulkhead Pattern demonstration"); + + // Create bulkhead services with different thread pool sizes + BulkheadService userService = new UserService(3); // 3 threads for user requests + BulkheadService backgroundService = new BackgroundService(2); // 2 threads for background tasks + + try { + // Scenario 1: Normal operation - both services handle requests successfully + LOGGER.info("\n=== Scenario 1: Normal Operation ==="); + demonstrateNormalOperation(userService, backgroundService); + + Thread.sleep(2000); // Wait for tasks to complete + + // Scenario 2: Overload background service - user service should remain unaffected + LOGGER.info("\n=== Scenario 2: Background Service Overload ==="); + demonstrateServiceOverload(userService, backgroundService); + + Thread.sleep(3000); // Wait for demonstration to complete + + // Scenario 3: Show resource isolation + LOGGER.info("\n=== Scenario 3: Resource Isolation ==="); + demonstrateResourceIsolation(userService, backgroundService); + + } catch (InterruptedException e) { + LOGGER.error("Demonstration interrupted", e); + Thread.currentThread().interrupt(); + } finally { + // Cleanup + LOGGER.info("\nShutting down services"); + userService.shutdown(); + backgroundService.shutdown(); + LOGGER.info("Bulkhead Pattern demonstration completed"); + } + } + + /** + * Demonstrates normal operation where both services handle requests successfully. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateNormalOperation( + BulkheadService userService, + BulkheadService backgroundService) { + + LOGGER.info("Submitting normal workload to both services"); + + // Submit tasks to user service + for (int i = 1; i <= 3; i++) { + Task task = new Task("User-Request-" + i, TaskType.USER_REQUEST, 500); + userService.submitTask(task); + } + + // Submit tasks to background service + for (int i = 1; i <= 2; i++) { + Task task = new Task("Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 700); + backgroundService.submitTask(task); + } + } + + /** + * Demonstrates overloading the background service while user service remains operational. + * This shows how the bulkhead pattern prevents cascade failures. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateServiceOverload( + BulkheadService userService, + BulkheadService backgroundService) { + + LOGGER.info("Overloading background service - user service should remain responsive"); + + // Flood background service with tasks (more than its capacity) + for (int i = 1; i <= 10; i++) { + Task task = new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000); + backgroundService.submitTask(task); + } + + // User service should still accept requests + for (int i = 1; i <= 3; i++) { + Task task = new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300); + boolean accepted = userService.submitTask(task); + LOGGER.info("User request {} accepted: {}", i, accepted); + } + } + + /** + * Demonstrates resource isolation by showing independent operation of services. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateResourceIsolation( + BulkheadService userService, + BulkheadService backgroundService) { + + LOGGER.info("Demonstrating resource isolation between services"); + LOGGER.info("User Service - Active threads: {}, Queue size: {}", + userService.getActiveThreads(), userService.getQueueSize()); + LOGGER.info("Background Service - Active threads: {}, Queue size: {}", + backgroundService.getActiveThreads(), backgroundService.getQueueSize()); + } +} \ No newline at end of file diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java new file mode 100644 index 000000000000..7817281326b2 --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java @@ -0,0 +1,38 @@ +package com.iluwatar.bulkhead; + +/** + * Service for handling background processing tasks with dedicated resources. + * + *

This service handles non-critical, asynchronous operations that can tolerate + * higher latency. By isolating its resources from user-facing services, failures + * or slowdowns in background processing don't impact critical user operations. + * + *

Example use cases: + *

+ */ +public class BackgroundService extends BulkheadService { + + private static final int DEFAULT_QUEUE_CAPACITY = 20; + + /** + * Creates a BackgroundService with specified thread pool size. + * + * @param maxThreads maximum number of threads for background processing + */ + public BackgroundService(int maxThreads) { + super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } + + @Override + protected void handleRejectedTask(Task task) { + // For background tasks, we might want to retry later or persist to a queue + super.handleRejectedTask(task); + // Additional background-specific rejection logic could go here + // e.g., persist to database for later retry + } +} diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java new file mode 100644 index 000000000000..2400b3fd506c --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java @@ -0,0 +1,143 @@ +package com.iluwatar.bulkhead; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract base class for services implementing the Bulkhead pattern. + * + *

This class provides resource isolation by maintaining a dedicated thread pool + * for each service. When the thread pool is exhausted, new tasks are rejected quickly + * (fail-fast behavior) rather than blocking or consuming resources from other services. + * + *

Key features: + *

+ */ +public abstract class BulkheadService { + + private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); + + protected final ThreadPoolExecutor executor; + protected final String serviceName; + private final int maxPoolSize; + private final int queueCapacity; + + /** + * Creates a new BulkheadService with specified thread pool configuration. + * + * @param serviceName name of the service for logging + * @param maxPoolSize maximum number of threads in the pool + * @param queueCapacity maximum number of tasks that can be queued + */ + protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { + this.serviceName = serviceName; + this.maxPoolSize = maxPoolSize; + this.queueCapacity = queueCapacity; + + // Create thread pool with bounded queue + this.executor = new ThreadPoolExecutor( + maxPoolSize, // core pool size + maxPoolSize, // maximum pool size + 60L, // keep alive time + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(queueCapacity), // bounded queue + new ThreadPoolExecutor.AbortPolicy() // reject when full + ); + + LOGGER.info("Created {} with {} threads and queue capacity {}", + serviceName, maxPoolSize, queueCapacity); + } + + /** + * Submits a task for execution. + * Returns false if the task is rejected due to resource exhaustion. + * + * @param task the task to execute + * @return true if task was accepted, false if rejected + */ + public boolean submitTask(Task task) { + try { + executor.execute(() -> processTask(task)); + LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); + return true; + } catch (RejectedExecutionException e) { + LOGGER.warn("[{}] Task '{}' REJECTED - bulkhead is full (fail-fast)", + serviceName, task.getName()); + handleRejectedTask(task); + return false; + } + } + + /** + * Processes the given task. Subclasses can override for custom behavior. + * + * @param task the task to process + */ + protected void processTask(Task task) { + LOGGER.info("[{}] Processing task '{}' (type: {}, duration: {}ms)", + serviceName, task.getName(), task.getType(), task.getDurationMs()); + + try { + // Simulate task processing + Thread.sleep(task.getDurationMs()); + LOGGER.info("[{}] Task '{}' completed successfully", serviceName, task.getName()); + } catch (InterruptedException e) { + LOGGER.error("[{}] Task '{}' interrupted", serviceName, task.getName()); + Thread.currentThread().interrupt(); + } + } + + /** + * Handles a rejected task. Subclasses can override for custom rejection handling. + * + * @param task the rejected task + */ + protected void handleRejectedTask(Task task) { + // Default: just log the rejection + LOGGER.info("[{}] Rejected task '{}' - consider retry or fallback logic", + serviceName, task.getName()); + } + + /** + * Gets the number of currently active threads. + * + * @return number of active threads + */ + public int getActiveThreads() { + return executor.getActiveCount(); + } + + /** + * Gets the current queue size. + * + * @return number of tasks in queue + */ + public int getQueueSize() { + return executor.getQueue().size(); + } + + /** + * Shuts down the thread pool gracefully. + */ + public void shutdown() { + LOGGER.info("[{}] Shutting down thread pool", serviceName); + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java new file mode 100644 index 000000000000..ef20bbdb9e9f --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java @@ -0,0 +1,60 @@ +package com.iluwatar.bulkhead; + +/** + * Represents a task to be executed by a bulkhead service. + * + *

Tasks are characterized by their name, type, and expected duration. + * This information is useful for logging, monitoring, and demonstrating + * the bulkhead pattern's behavior under different loads. + */ +public class Task { + + private final String name; + private final TaskType type; + private final long durationMs; + + /** + * Creates a new Task. + * + * @param name unique identifier for the task + * @param type the type of task (user request or background processing) + * @param durationMs expected duration in milliseconds + */ + public Task(String name, TaskType type, long durationMs) { + this.name = name; + this.type = type; + this.durationMs = durationMs; + } + + /** + * Gets the task name. + * + * @return task name + */ + public String getName() { + return name; + } + + /** + * Gets the task type. + * + * @return task type + */ + public TaskType getType() { + return type; + } + + /** + * Gets the expected task duration. + * + * @return duration in milliseconds + */ + public long getDurationMs() { + return durationMs; + } + + @Override + public String toString() { + return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; + } +} diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java new file mode 100644 index 000000000000..7b6d44d1d075 --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java @@ -0,0 +1,21 @@ +package com.iluwatar.bulkhead; + +/** + * Enumeration of task types in the system. + * + *

Different task types may have different priorities, resource requirements, + * and SLA expectations. The bulkhead pattern allows us to isolate resources + * based on these types. + */ +public enum TaskType { + + /** + * User-facing requests that require low latency and high availability. + */ + USER_REQUEST, + + /** + * Background processing tasks that can tolerate higher latency. + */ + BACKGROUND_PROCESSING +} diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java new file mode 100644 index 000000000000..9fe589880331 --- /dev/null +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java @@ -0,0 +1,38 @@ +package com.iluwatar.bulkhead; + +/** + * Service for handling user-facing requests with dedicated resources. + * + *

This service represents critical user interactions that require high availability + * and fast response times. By isolating its resources using the Bulkhead pattern, + * it remains responsive even when other services (like background processing) are + * experiencing issues or heavy load. + * + *

Example use cases: + *

+ */ +public class UserService extends BulkheadService { + + private static final int DEFAULT_QUEUE_CAPACITY = 10; + + /** + * Creates a UserService with specified thread pool size. + * + * @param maxThreads maximum number of threads for handling user requests + */ + public UserService(int maxThreads) { + super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } + + @Override + protected void handleRejectedTask(Task task) { + // For user-facing requests, we might want to return an error to the user + // or implement a fallback mechanism + super.handleRejectedTask(task); + // Additional user-specific rejection logic could go here + } +} From d812403c38f1b1f8f76cd9d02260e5d1eaa406fe Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey <61098642+beingadish@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:31:35 +0530 Subject: [PATCH 2/6] build: add microservices-bulkhead module to root pom --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 87aa35d6aa1e..d5bf8a9b3526 100644 --- a/pom.xml +++ b/pom.xml @@ -164,6 +164,7 @@ metadata-mapping microservices-aggregrator microservices-api-gateway + microservices-bulkhead microservices-client-side-ui-composition microservices-distributed-tracing microservices-idempotent-consumer From 59d12282de11382d35878d9f309f335b2726bff2 Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey <61098642+beingadish@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:42:53 +0530 Subject: [PATCH 3/6] build: update dependencies in microservices-bulkhead * Remove `org.projectlombok:lombok` dependency. * Add explicit version `3.27.7` to the `org.assertj:assertj-core` test dependency. --- microservices-bulkhead/pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/microservices-bulkhead/pom.xml b/microservices-bulkhead/pom.xml index 0d11453a0527..8f5cd2aecfc7 100644 --- a/microservices-bulkhead/pom.xml +++ b/microservices-bulkhead/pom.xml @@ -65,13 +65,6 @@ runtime - - - org.projectlombok - lombok - provided - - org.junit.jupiter @@ -97,6 +90,7 @@ org.assertj assertj-core + 3.27.7 test From 73fe98302400cd3a924ed3198b1e1974c49fb79c Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey <61098642+beingadish@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:22:10 +0530 Subject: [PATCH 4/6] feat(microservices-bulkhead): refactor implementation and add unit tests Refactor all core classes and add comprehensive test coverage for the Microservices Bulkhead pattern. Refactored: - BulkheadService: overhaul thread pool isolation logic - App: update demo to reflect revised service APIs - BackgroundService, UserService: align with updated BulkheadService - Task, TaskType: revise model structure and toString representation - pom.xml: update module dependency configuration Added tests: - AppTest: verifies main entry point runs without exception - BulkheadServiceTest: task acceptance, overflow rejection, active thread count, queue size reporting, and graceful shutdown - BackgroundServiceTest: task submission, bulkhead overflow, and isolation from UserService overload - UserServiceTest: user request acceptance and isolation from overloaded BackgroundService - TaskTest: name, type, duration getters and toString format - TaskTypeTest: enum values USER_REQUEST and BACKGROUND_PROCESSING --- .../main/java/com/iluwatar/bulkhead/App.java | 204 +++++++--------- .../iluwatar/bulkhead/BackgroundService.java | 49 ++-- .../iluwatar/bulkhead/BulkheadService.java | 231 +++++++++--------- .../main/java/com/iluwatar/bulkhead/Task.java | 93 +++---- .../java/com/iluwatar/bulkhead/TaskType.java | 20 +- .../com/iluwatar/bulkhead/UserService.java | 48 ++-- .../java/com/iluwatar/bulkhead/AppTest.java | 32 +++ .../bulkhead/BackgroundServiceTest.java | 74 ++++++ .../bulkhead/BulkheadServiceTest.java | 79 ++++++ .../java/com/iluwatar/bulkhead/TaskTest.java | 53 ++++ .../com/iluwatar/bulkhead/TaskTypeTest.java | 43 ++++ .../iluwatar/bulkhead/UserServiceTest.java | 60 +++++ pom.xml | 2 +- 13 files changed, 661 insertions(+), 327 deletions(-) create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java create mode 100644 microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java index 194a52f8e819..03f04bf2b7d2 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java @@ -1,137 +1,123 @@ package com.iluwatar.bulkhead; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The Bulkhead pattern isolates critical system resources for each service or component - * to prevent failures or heavy load in one part from cascading and degrading the entire system. + * The Bulkhead pattern isolates critical system resources for each service or + * component to prevent failures or heavy load in one part from cascading and + * degrading the entire system. * - *

In this example, we demonstrate resource isolation using separate thread pools for - * different services. The {@link UserService} handles user-facing requests while - * {@link BackgroundService} handles background tasks. Each service has its own dedicated - * thread pool (bulkhead), ensuring that if one service becomes overloaded, the other - * continues to function normally. + *

+ * In this example, we demonstrate resource isolation using separate thread + * pools for different services. The {@link UserService} handles user-facing + * requests while {@link BackgroundService} handles background tasks. Each + * service has its own dedicated thread pool (bulkhead), ensuring that if one + * service becomes overloaded, the other continues to function normally. + * + *

+ * Key concepts demonstrated: * - *

Key concepts demonstrated: *

*/ public class App { - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); - /** - * Program entry point. - * - * @param args command line arguments - */ - public static void main(String[] args) { - LOGGER.info("Starting Bulkhead Pattern demonstration"); + /** + * Program entry point. + * + * @param args command line arguments + */ + public static void main(String[] args) throws InterruptedException { + LOGGER.info("Starting Bulkhead Pattern demonstration"); - // Create bulkhead services with different thread pool sizes - BulkheadService userService = new UserService(3); // 3 threads for user requests - BulkheadService backgroundService = new BackgroundService(2); // 2 threads for background tasks + BulkheadService userService = new UserService(3); + BulkheadService backgroundService = new BackgroundService(2); - try { - // Scenario 1: Normal operation - both services handle requests successfully - LOGGER.info("\n=== Scenario 1: Normal Operation ==="); - demonstrateNormalOperation(userService, backgroundService); + try { + LOGGER.info("\n=== Scenario 1: Normal Operation ==="); + demonstrateNormalOperation(userService, backgroundService); - Thread.sleep(2000); // Wait for tasks to complete + Thread.sleep(2000); - // Scenario 2: Overload background service - user service should remain unaffected - LOGGER.info("\n=== Scenario 2: Background Service Overload ==="); - demonstrateServiceOverload(userService, backgroundService); + LOGGER.info("\n=== Scenario 2: Background Service Overload ==="); + demonstrateServiceOverload(userService, backgroundService); - Thread.sleep(3000); // Wait for demonstration to complete + Thread.sleep(3000); - // Scenario 3: Show resource isolation - LOGGER.info("\n=== Scenario 3: Resource Isolation ==="); - demonstrateResourceIsolation(userService, backgroundService); + LOGGER.info("\n=== Scenario 3: Resource Isolation ==="); + demonstrateResourceIsolation(userService, backgroundService); - } catch (InterruptedException e) { - LOGGER.error("Demonstration interrupted", e); - Thread.currentThread().interrupt(); - } finally { - // Cleanup - LOGGER.info("\nShutting down services"); - userService.shutdown(); - backgroundService.shutdown(); - LOGGER.info("Bulkhead Pattern demonstration completed"); + } finally { + userService.shutdown(); + backgroundService.shutdown(); + LOGGER.info("Bulkhead Pattern demonstration completed"); + } } - } - - /** - * Demonstrates normal operation where both services handle requests successfully. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateNormalOperation( - BulkheadService userService, - BulkheadService backgroundService) { - - LOGGER.info("Submitting normal workload to both services"); - // Submit tasks to user service - for (int i = 1; i <= 3; i++) { - Task task = new Task("User-Request-" + i, TaskType.USER_REQUEST, 500); - userService.submitTask(task); + /** + * Demonstrates normal operation where both services handle requests + * successfully. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateNormalOperation( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Submitting normal workload to both services"); + for (int i = 1; i <= 3; i++) { + userService.submitTask(new Task("User-Request-" + i, TaskType.USER_REQUEST, 500)); + } + for (int i = 1; i <= 2; i++) { + backgroundService.submitTask( + new Task("Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 700)); + } } - // Submit tasks to background service - for (int i = 1; i <= 2; i++) { - Task task = new Task("Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 700); - backgroundService.submitTask(task); + /** + * Demonstrates overloading the background service while user service + * remains operational. This shows how the bulkhead pattern prevents cascade + * failures. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateServiceOverload( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Overloading background service - user service should remain responsive"); + for (int i = 1; i <= 10; i++) { + backgroundService.submitTask( + new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000)); + } + for (int i = 1; i <= 3; i++) { + boolean accepted + = userService.submitTask(new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300)); + LOGGER.info("User request {} accepted: {}", i, accepted); + } } - } - - /** - * Demonstrates overloading the background service while user service remains operational. - * This shows how the bulkhead pattern prevents cascade failures. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateServiceOverload( - BulkheadService userService, - BulkheadService backgroundService) { - - LOGGER.info("Overloading background service - user service should remain responsive"); - // Flood background service with tasks (more than its capacity) - for (int i = 1; i <= 10; i++) { - Task task = new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000); - backgroundService.submitTask(task); + /** + * Demonstrates resource isolation by showing independent operation of + * services. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateResourceIsolation( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Demonstrating resource isolation between services"); + LOGGER.info( + "User Service - Active threads: {}, Queue size: {}", + userService.getActiveThreads(), + userService.getQueueSize()); + LOGGER.info( + "Background Service - Active threads: {}, Queue size: {}", + backgroundService.getActiveThreads(), + backgroundService.getQueueSize()); } - - // User service should still accept requests - for (int i = 1; i <= 3; i++) { - Task task = new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300); - boolean accepted = userService.submitTask(task); - LOGGER.info("User request {} accepted: {}", i, accepted); - } - } - - /** - * Demonstrates resource isolation by showing independent operation of services. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateResourceIsolation( - BulkheadService userService, - BulkheadService backgroundService) { - - LOGGER.info("Demonstrating resource isolation between services"); - LOGGER.info("User Service - Active threads: {}, Queue size: {}", - userService.getActiveThreads(), userService.getQueueSize()); - LOGGER.info("Background Service - Active threads: {}, Queue size: {}", - backgroundService.getActiveThreads(), backgroundService.getQueueSize()); - } -} \ No newline at end of file +} diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java index 7817281326b2..5286a5365941 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java @@ -3,36 +3,37 @@ /** * Service for handling background processing tasks with dedicated resources. * - *

This service handles non-critical, asynchronous operations that can tolerate - * higher latency. By isolating its resources from user-facing services, failures - * or slowdowns in background processing don't impact critical user operations. + *

+ * This service handles non-critical, asynchronous operations that can tolerate + * higher latency. By isolating its resources from user-facing services, + * failures or slowdowns in background processing don't impact critical user + * operations. + * + *

+ * Example use cases: * - *

Example use cases: *

*/ public class BackgroundService extends BulkheadService { - private static final int DEFAULT_QUEUE_CAPACITY = 20; + private static final int DEFAULT_QUEUE_CAPACITY = 20; - /** - * Creates a BackgroundService with specified thread pool size. - * - * @param maxThreads maximum number of threads for background processing - */ - public BackgroundService(int maxThreads) { - super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); - } + /** + * Creates a BackgroundService with specified thread pool size. + * + * @param maxThreads maximum number of threads for background processing + */ + public BackgroundService(int maxThreads) { + super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } - @Override - protected void handleRejectedTask(Task task) { - // For background tasks, we might want to retry later or persist to a queue - super.handleRejectedTask(task); - // Additional background-specific rejection logic could go here - // e.g., persist to database for later retry - } + @Override + protected void handleRejectedTask(Task task) { + super.handleRejectedTask(task); + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java index 2400b3fd506c..9124e8a0ca5f 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java @@ -4,140 +4,145 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base class for services implementing the Bulkhead pattern. * - *

This class provides resource isolation by maintaining a dedicated thread pool - * for each service. When the thread pool is exhausted, new tasks are rejected quickly - * (fail-fast behavior) rather than blocking or consuming resources from other services. + *

+ * This class provides resource isolation by maintaining a dedicated thread pool + * for each service. When the thread pool is exhausted, new tasks are rejected + * quickly (fail-fast behavior) rather than blocking or consuming resources from + * other services. + * + *

+ * Key features: * - *

Key features: *

*/ public abstract class BulkheadService { - private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); - - protected final ThreadPoolExecutor executor; - protected final String serviceName; - private final int maxPoolSize; - private final int queueCapacity; - - /** - * Creates a new BulkheadService with specified thread pool configuration. - * - * @param serviceName name of the service for logging - * @param maxPoolSize maximum number of threads in the pool - * @param queueCapacity maximum number of tasks that can be queued - */ - protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { - this.serviceName = serviceName; - this.maxPoolSize = maxPoolSize; - this.queueCapacity = queueCapacity; - - // Create thread pool with bounded queue - this.executor = new ThreadPoolExecutor( - maxPoolSize, // core pool size - maxPoolSize, // maximum pool size - 60L, // keep alive time - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(queueCapacity), // bounded queue - new ThreadPoolExecutor.AbortPolicy() // reject when full - ); + private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); - LOGGER.info("Created {} with {} threads and queue capacity {}", - serviceName, maxPoolSize, queueCapacity); - } + protected final ThreadPoolExecutor executor; + protected final String serviceName; - /** - * Submits a task for execution. - * Returns false if the task is rejected due to resource exhaustion. - * - * @param task the task to execute - * @return true if task was accepted, false if rejected - */ - public boolean submitTask(Task task) { - try { - executor.execute(() -> processTask(task)); - LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); - return true; - } catch (RejectedExecutionException e) { - LOGGER.warn("[{}] Task '{}' REJECTED - bulkhead is full (fail-fast)", - serviceName, task.getName()); - handleRejectedTask(task); - return false; + /** + * Creates a new BulkheadService with specified thread pool configuration. + * + * @param serviceName name of the service for logging + * @param maxPoolSize maximum number of threads in the pool + * @param queueCapacity maximum number of tasks that can be queued + */ + protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { + this.serviceName = serviceName; + this.executor + = new ThreadPoolExecutor( + maxPoolSize, + maxPoolSize, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(queueCapacity), + new ThreadPoolExecutor.AbortPolicy()); + LOGGER.info( + "Created {} with {} threads and queue capacity {}", + serviceName, + maxPoolSize, + queueCapacity); } - } - /** - * Processes the given task. Subclasses can override for custom behavior. - * - * @param task the task to process - */ - protected void processTask(Task task) { - LOGGER.info("[{}] Processing task '{}' (type: {}, duration: {}ms)", - serviceName, task.getName(), task.getType(), task.getDurationMs()); + /** + * Submits a task for execution. Returns false if the task is rejected due + * to resource exhaustion. + * + * @param task the task to execute + * @return true if task was accepted, false if rejected + */ + public boolean submitTask(Task task) { + try { + executor.execute(() -> processTask(task)); + LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); + return true; + } catch (RejectedExecutionException e) { + LOGGER.warn( + "[{}] Task '{}' REJECTED - bulkhead is full (fail-fast)", serviceName, task.getName()); + handleRejectedTask(task); + return false; + } + } - try { - // Simulate task processing - Thread.sleep(task.getDurationMs()); - LOGGER.info("[{}] Task '{}' completed successfully", serviceName, task.getName()); - } catch (InterruptedException e) { - LOGGER.error("[{}] Task '{}' interrupted", serviceName, task.getName()); - Thread.currentThread().interrupt(); + /** + * Processes the given task. Subclasses can override for custom behavior. + * + * @param task the task to process + */ + protected void processTask(Task task) { + LOGGER.info( + "[{}] Processing task '{}' (type: {}, duration: {}ms)", + serviceName, + task.getName(), + task.getType(), + task.getDurationMs()); + try { + Thread.sleep(task.getDurationMs()); + LOGGER.info("[{}] Task '{}' completed successfully", serviceName, task.getName()); + } catch (InterruptedException e) { + LOGGER.error("[{}] Task '{}' interrupted", serviceName, task.getName()); + Thread.currentThread().interrupt(); + } } - } - /** - * Handles a rejected task. Subclasses can override for custom rejection handling. - * - * @param task the rejected task - */ - protected void handleRejectedTask(Task task) { - // Default: just log the rejection - LOGGER.info("[{}] Rejected task '{}' - consider retry or fallback logic", - serviceName, task.getName()); - } + /** + * Handles a rejected task. Subclasses can override for custom rejection + * handling. + * + * @param task the rejected task + */ + protected void handleRejectedTask(Task task) { + LOGGER.info( + "[{}] Rejected task '{}' - consider retry or fallback logic", + serviceName, + task.getName()); + } - /** - * Gets the number of currently active threads. - * - * @return number of active threads - */ - public int getActiveThreads() { - return executor.getActiveCount(); - } + /** + * Gets the number of currently active threads. + * + * @return number of active threads + */ + public int getActiveThreads() { + return executor.getActiveCount(); + } - /** - * Gets the current queue size. - * - * @return number of tasks in queue - */ - public int getQueueSize() { - return executor.getQueue().size(); - } + /** + * Gets the current queue size. + * + * @return number of tasks in queue + */ + public int getQueueSize() { + return executor.getQueue().size(); + } - /** - * Shuts down the thread pool gracefully. - */ - public void shutdown() { - LOGGER.info("[{}] Shutting down thread pool", serviceName); - executor.shutdown(); - try { - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - executor.shutdownNow(); - } - } catch (InterruptedException e) { - executor.shutdownNow(); - Thread.currentThread().interrupt(); + /** + * Shuts down the thread pool gracefully. + */ + public void shutdown() { + LOGGER.info("[{}] Shutting down thread pool", serviceName); + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } } - } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java index ef20bbdb9e9f..6ed66a2355cd 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java @@ -3,58 +3,59 @@ /** * Represents a task to be executed by a bulkhead service. * - *

Tasks are characterized by their name, type, and expected duration. - * This information is useful for logging, monitoring, and demonstrating - * the bulkhead pattern's behavior under different loads. + *

+ * Tasks are characterized by their name, type, and expected duration. This + * information is useful for logging, monitoring, and demonstrating the bulkhead + * pattern's behavior under different loads. */ public class Task { - private final String name; - private final TaskType type; - private final long durationMs; + private final String name; + private final TaskType type; + private final long durationMs; - /** - * Creates a new Task. - * - * @param name unique identifier for the task - * @param type the type of task (user request or background processing) - * @param durationMs expected duration in milliseconds - */ - public Task(String name, TaskType type, long durationMs) { - this.name = name; - this.type = type; - this.durationMs = durationMs; - } + /** + * Creates a new Task. + * + * @param name unique identifier for the task + * @param type the type of task (user request or background processing) + * @param durationMs expected duration in milliseconds + */ + public Task(String name, TaskType type, long durationMs) { + this.name = name; + this.type = type; + this.durationMs = durationMs; + } - /** - * Gets the task name. - * - * @return task name - */ - public String getName() { - return name; - } + /** + * Gets the task name. + * + * @return task name + */ + public String getName() { + return name; + } - /** - * Gets the task type. - * - * @return task type - */ - public TaskType getType() { - return type; - } + /** + * Gets the task type. + * + * @return task type + */ + public TaskType getType() { + return type; + } - /** - * Gets the expected task duration. - * - * @return duration in milliseconds - */ - public long getDurationMs() { - return durationMs; - } + /** + * Gets the expected task duration. + * + * @return duration in milliseconds + */ + public long getDurationMs() { + return durationMs; + } - @Override - public String toString() { - return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; - } + @Override + public String toString() { + return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java index 7b6d44d1d075..8a6884b736d4 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java @@ -3,19 +3,19 @@ /** * Enumeration of task types in the system. * - *

Different task types may have different priorities, resource requirements, + *

+ * Different task types may have different priorities, resource requirements, * and SLA expectations. The bulkhead pattern allows us to isolate resources * based on these types. */ public enum TaskType { - /** - * User-facing requests that require low latency and high availability. - */ - USER_REQUEST, - - /** - * Background processing tasks that can tolerate higher latency. - */ - BACKGROUND_PROCESSING + /** + * User-facing requests that require low latency and high availability. + */ + USER_REQUEST, + /** + * Background processing tasks that can tolerate higher latency. + */ + BACKGROUND_PROCESSING } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java index 9fe589880331..08b5e27a6152 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java @@ -3,36 +3,36 @@ /** * Service for handling user-facing requests with dedicated resources. * - *

This service represents critical user interactions that require high availability - * and fast response times. By isolating its resources using the Bulkhead pattern, - * it remains responsive even when other services (like background processing) are - * experiencing issues or heavy load. + *

+ * This service represents critical user interactions that require high + * availability and fast response times. By isolating its resources using the + * Bulkhead pattern, it remains responsive even when other services (like + * background processing) are experiencing issues or heavy load. + * + *

+ * Example use cases: * - *

Example use cases: *

*/ public class UserService extends BulkheadService { - private static final int DEFAULT_QUEUE_CAPACITY = 10; + private static final int DEFAULT_QUEUE_CAPACITY = 10; - /** - * Creates a UserService with specified thread pool size. - * - * @param maxThreads maximum number of threads for handling user requests - */ - public UserService(int maxThreads) { - super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); - } + /** + * Creates a UserService with specified thread pool size. + * + * @param maxThreads maximum number of threads for handling user requests + */ + public UserService(int maxThreads) { + super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } - @Override - protected void handleRejectedTask(Task task) { - // For user-facing requests, we might want to return an error to the user - // or implement a fallback mechanism - super.handleRejectedTask(task); - // Additional user-specific rejection logic could go here - } + @Override + protected void handleRejectedTask(Task task) { + super.handleRejectedTask(task); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java new file mode 100644 index 000000000000..bdea91e52de7 --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java @@ -0,0 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import org.junit.jupiter.api.Test; + +class AppTest { + + @Test + void shouldRunWithoutException() throws Exception { + App.main(new String[]{}); + } +} diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java new file mode 100644 index 000000000000..dbd6690a073b --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java @@ -0,0 +1,74 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BackgroundServiceTest { + + private BackgroundService backgroundService; + + @BeforeEach + void setUp() { + backgroundService = new BackgroundService(2); + } + + @AfterEach + void tearDown() { + backgroundService.shutdown(); + } + + @Test + void shouldAcceptBackgroundTask() { + assertThat( + backgroundService.submitTask( + new Task("bg-job-1", TaskType.BACKGROUND_PROCESSING, 0))) + .isTrue(); + } + + @Test + void shouldRejectWhenBulkheadIsFull() { + for (int i = 0; i < 22; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 5000)); + } + assertThat( + backgroundService.submitTask( + new Task("overflow", TaskType.BACKGROUND_PROCESSING, 100))) + .isFalse(); + } + + @Test + void overloadShouldNotAffectUserService() { + var userService = new UserService(2); + try { + for (int i = 0; i < 25; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); + } + assertThat(userService.submitTask(new Task("user-req", TaskType.USER_REQUEST, 0))).isTrue(); + } finally { + userService.shutdown(); + } + } +} diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java new file mode 100644 index 000000000000..72704c4146dd --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java @@ -0,0 +1,79 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BulkheadServiceTest { + + private UserService service; + + @BeforeEach + void setUp() { + service = new UserService(2); + } + + @AfterEach + void tearDown() { + service.shutdown(); + } + + @Test + void shouldAcceptTaskWhenCapacityAvailable() { + var task = new Task("task-1", TaskType.USER_REQUEST, 0); + assertThat(service.submitTask(task)).isTrue(); + } + + @Test + void shouldRejectTaskWhenBulkheadIsFull() { + for (int i = 0; i < 12; i++) { + service.submitTask(new Task("blocker-" + i, TaskType.USER_REQUEST, 5000)); + } + assertThat(service.submitTask(new Task("overflow", TaskType.USER_REQUEST, 100))).isFalse(); + } + + @Test + void shouldReportActiveThreadCount() throws InterruptedException { + service.submitTask(new Task("t1", TaskType.USER_REQUEST, 500)); + service.submitTask(new Task("t2", TaskType.USER_REQUEST, 500)); + Thread.sleep(100); + assertThat(service.getActiveThreads()).isGreaterThanOrEqualTo(0); + } + + @Test + void shouldReportQueueSize() throws InterruptedException { + service.submitTask(new Task("t1", TaskType.USER_REQUEST, 2000)); + service.submitTask(new Task("t2", TaskType.USER_REQUEST, 2000)); + service.submitTask(new Task("queued", TaskType.USER_REQUEST, 2000)); + Thread.sleep(100); + assertThat(service.getQueueSize()).isGreaterThanOrEqualTo(0); + } + + @Test + void shutdownShouldCompleteWithoutError() { + service.submitTask(new Task("t", TaskType.USER_REQUEST, 0)); + service.shutdown(); + } +} diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java new file mode 100644 index 000000000000..705e36590161 --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +class TaskTest { + + @Test + void shouldReturnCorrectName() { + var task = new Task("my-task", TaskType.USER_REQUEST, 100); + assertThat(task.getName()).isEqualTo("my-task"); + } + + @Test + void shouldReturnCorrectType() { + var task = new Task("t", TaskType.BACKGROUND_PROCESSING, 50); + assertThat(task.getType()).isEqualTo(TaskType.BACKGROUND_PROCESSING); + } + + @Test + void shouldReturnCorrectDuration() { + var task = new Task("t", TaskType.USER_REQUEST, 250); + assertThat(task.getDurationMs()).isEqualTo(250); + } + + @Test + void toStringShouldContainNameTypeAndDuration() { + var task = new Task("my-task", TaskType.USER_REQUEST, 100); + var str = task.toString(); + assertThat(str).contains("my-task").contains("USER_REQUEST").contains("100"); + } +} diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java new file mode 100644 index 000000000000..53d8d540ab5f --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java @@ -0,0 +1,43 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +class TaskTypeTest { + + @Test + void shouldHaveTwoValues() { + assertThat(TaskType.values()).hasSize(2); + } + + @Test + void shouldContainUserRequest() { + assertThat(TaskType.valueOf("USER_REQUEST")).isEqualTo(TaskType.USER_REQUEST); + } + + @Test + void shouldContainBackgroundProcessing() { + assertThat(TaskType.valueOf("BACKGROUND_PROCESSING")).isEqualTo(TaskType.BACKGROUND_PROCESSING); + } +} diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java new file mode 100644 index 000000000000..1d78fbae4773 --- /dev/null +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java @@ -0,0 +1,60 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.iluwatar.bulkhead; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UserServiceTest { + + private UserService userService; + + @BeforeEach + void setUp() { + userService = new UserService(2); + } + + @AfterEach + void tearDown() { + userService.shutdown(); + } + + @Test + void shouldAcceptUserRequestTask() { + assertThat(userService.submitTask(new Task("user-req-1", TaskType.USER_REQUEST, 0))).isTrue(); + } + + @Test + void shouldIsolateFromOverloadedOtherService() { + var backgroundService = new BackgroundService(1); + try { + for (int i = 0; i < 25; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); + } + assertThat(userService.submitTask(new Task("critical", TaskType.USER_REQUEST, 0))).isTrue(); + } finally { + backgroundService.shutdown(); + } + } +} diff --git a/pom.xml b/pom.xml index d5bf8a9b3526..9798f36ef09d 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,7 @@ metadata-mapping microservices-aggregrator microservices-api-gateway - microservices-bulkhead + microservices-bulkhead microservices-client-side-ui-composition microservices-distributed-tracing microservices-idempotent-consumer From c1951c434a93b7dbb6dbd4c3c7af84b30f8e363c Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey <61098642+beingadish@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:23:47 +0530 Subject: [PATCH 5/6] Refactored: - Task: used Lombok @Getter --- .../main/java/com/iluwatar/bulkhead/Task.java | 45 +++---------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java index 6ed66a2355cd..f3cd40ad6e74 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java @@ -1,5 +1,8 @@ package com.iluwatar.bulkhead; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + /** * Represents a task to be executed by a bulkhead service. * @@ -8,52 +11,14 @@ * information is useful for logging, monitoring, and demonstrating the bulkhead * pattern's behavior under different loads. */ +@Getter +@RequiredArgsConstructor public class Task { private final String name; private final TaskType type; private final long durationMs; - /** - * Creates a new Task. - * - * @param name unique identifier for the task - * @param type the type of task (user request or background processing) - * @param durationMs expected duration in milliseconds - */ - public Task(String name, TaskType type, long durationMs) { - this.name = name; - this.type = type; - this.durationMs = durationMs; - } - - /** - * Gets the task name. - * - * @return task name - */ - public String getName() { - return name; - } - - /** - * Gets the task type. - * - * @return task type - */ - public TaskType getType() { - return type; - } - - /** - * Gets the expected task duration. - * - * @return duration in milliseconds - */ - public long getDurationMs() { - return durationMs; - } - @Override public String toString() { return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; From 9d51b474534f271e569d8bf5a52adfb6b5ff9f70 Mon Sep 17 00:00:00 2001 From: Aadarsh Pandey <61098642+beingadish@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:59:14 +0530 Subject: [PATCH 6/6] style: add license headers and apply Google Java Format --- .../main/java/com/iluwatar/bulkhead/App.java | 203 ++++++++------- .../iluwatar/bulkhead/BackgroundService.java | 66 +++-- .../iluwatar/bulkhead/BulkheadService.java | 245 +++++++++--------- .../main/java/com/iluwatar/bulkhead/Task.java | 41 ++- .../java/com/iluwatar/bulkhead/TaskType.java | 39 ++- .../com/iluwatar/bulkhead/UserService.java | 64 +++-- .../java/com/iluwatar/bulkhead/AppTest.java | 8 +- .../bulkhead/BackgroundServiceTest.java | 71 +++-- .../bulkhead/BulkheadServiceTest.java | 81 +++--- .../java/com/iluwatar/bulkhead/TaskTest.java | 43 +-- .../com/iluwatar/bulkhead/TaskTypeTest.java | 25 +- .../iluwatar/bulkhead/UserServiceTest.java | 49 ++-- 12 files changed, 517 insertions(+), 418 deletions(-) diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java index 03f04bf2b7d2..b9b7e207681c 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/App.java @@ -1,123 +1,138 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The Bulkhead pattern isolates critical system resources for each service or - * component to prevent failures or heavy load in one part from cascading and - * degrading the entire system. + * The Bulkhead pattern isolates critical system resources for each service or component to prevent + * failures or heavy load in one part from cascading and degrading the entire system. * - *

- * In this example, we demonstrate resource isolation using separate thread - * pools for different services. The {@link UserService} handles user-facing - * requests while {@link BackgroundService} handles background tasks. Each - * service has its own dedicated thread pool (bulkhead), ensuring that if one - * service becomes overloaded, the other continues to function normally. + *

In this example, we demonstrate resource isolation using separate thread pools for different + * services. The {@link UserService} handles user-facing requests while {@link BackgroundService} + * handles background tasks. Each service has its own dedicated thread pool (bulkhead), ensuring + * that if one service becomes overloaded, the other continues to function normally. * - *

- * Key concepts demonstrated: + *

Key concepts demonstrated: * *

*/ public class App { - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); - /** - * Program entry point. - * - * @param args command line arguments - */ - public static void main(String[] args) throws InterruptedException { - LOGGER.info("Starting Bulkhead Pattern demonstration"); + /** + * Program entry point. + * + * @param args command line arguments + */ + public static void main(String[] args) throws InterruptedException { + LOGGER.info("Starting Bulkhead Pattern demonstration"); - BulkheadService userService = new UserService(3); - BulkheadService backgroundService = new BackgroundService(2); + BulkheadService userService = new UserService(3); + BulkheadService backgroundService = new BackgroundService(2); - try { - LOGGER.info("\n=== Scenario 1: Normal Operation ==="); - demonstrateNormalOperation(userService, backgroundService); + try { + LOGGER.info("\n=== Scenario 1: Normal Operation ==="); + demonstrateNormalOperation(userService, backgroundService); - Thread.sleep(2000); + Thread.sleep(2000); - LOGGER.info("\n=== Scenario 2: Background Service Overload ==="); - demonstrateServiceOverload(userService, backgroundService); + LOGGER.info("\n=== Scenario 2: Background Service Overload ==="); + demonstrateServiceOverload(userService, backgroundService); - Thread.sleep(3000); + Thread.sleep(3000); - LOGGER.info("\n=== Scenario 3: Resource Isolation ==="); - demonstrateResourceIsolation(userService, backgroundService); + LOGGER.info("\n=== Scenario 3: Resource Isolation ==="); + demonstrateResourceIsolation(userService, backgroundService); - } finally { - userService.shutdown(); - backgroundService.shutdown(); - LOGGER.info("Bulkhead Pattern demonstration completed"); - } + } finally { + userService.shutdown(); + backgroundService.shutdown(); + LOGGER.info("Bulkhead Pattern demonstration completed"); } + } - /** - * Demonstrates normal operation where both services handle requests - * successfully. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateNormalOperation( - BulkheadService userService, BulkheadService backgroundService) { - LOGGER.info("Submitting normal workload to both services"); - for (int i = 1; i <= 3; i++) { - userService.submitTask(new Task("User-Request-" + i, TaskType.USER_REQUEST, 500)); - } - for (int i = 1; i <= 2; i++) { - backgroundService.submitTask( - new Task("Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 700)); - } + /** + * Demonstrates normal operation where both services handle requests successfully. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateNormalOperation( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Submitting normal workload to both services"); + for (int i = 1; i <= 3; i++) { + userService.submitTask(new Task("User-Request-" + i, TaskType.USER_REQUEST, 500)); } - - /** - * Demonstrates overloading the background service while user service - * remains operational. This shows how the bulkhead pattern prevents cascade - * failures. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateServiceOverload( - BulkheadService userService, BulkheadService backgroundService) { - LOGGER.info("Overloading background service - user service should remain responsive"); - for (int i = 1; i <= 10; i++) { - backgroundService.submitTask( - new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000)); - } - for (int i = 1; i <= 3; i++) { - boolean accepted - = userService.submitTask(new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300)); - LOGGER.info("User request {} accepted: {}", i, accepted); - } + for (int i = 1; i <= 2; i++) { + backgroundService.submitTask( + new Task("Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 700)); } + } - /** - * Demonstrates resource isolation by showing independent operation of - * services. - * - * @param userService the user-facing service - * @param backgroundService the background processing service - */ - private static void demonstrateResourceIsolation( - BulkheadService userService, BulkheadService backgroundService) { - LOGGER.info("Demonstrating resource isolation between services"); - LOGGER.info( - "User Service - Active threads: {}, Queue size: {}", - userService.getActiveThreads(), - userService.getQueueSize()); - LOGGER.info( - "Background Service - Active threads: {}, Queue size: {}", - backgroundService.getActiveThreads(), - backgroundService.getQueueSize()); + /** + * Demonstrates overloading the background service while user service remains operational. This + * shows how the bulkhead pattern prevents cascade failures. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateServiceOverload( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Overloading background service - user service should remain responsive"); + for (int i = 1; i <= 10; i++) { + backgroundService.submitTask( + new Task("Heavy-Background-Job-" + i, TaskType.BACKGROUND_PROCESSING, 2000)); } + for (int i = 1; i <= 3; i++) { + boolean accepted = + userService.submitTask( + new Task("Critical-User-Request-" + i, TaskType.USER_REQUEST, 300)); + LOGGER.info("User request {} accepted: {}", i, accepted); + } + } + + /** + * Demonstrates resource isolation by showing independent operation of services. + * + * @param userService the user-facing service + * @param backgroundService the background processing service + */ + private static void demonstrateResourceIsolation( + BulkheadService userService, BulkheadService backgroundService) { + LOGGER.info("Demonstrating resource isolation between services"); + LOGGER.info( + "User Service - Active threads: {}, Queue size: {}", + userService.getActiveThreads(), + userService.getQueueSize()); + LOGGER.info( + "Background Service - Active threads: {}, Queue size: {}", + backgroundService.getActiveThreads(), + backgroundService.getQueueSize()); + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java index 5286a5365941..84cdfe32f843 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BackgroundService.java @@ -1,39 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; /** * Service for handling background processing tasks with dedicated resources. * - *

- * This service handles non-critical, asynchronous operations that can tolerate - * higher latency. By isolating its resources from user-facing services, - * failures or slowdowns in background processing don't impact critical user - * operations. + *

This service handles non-critical, asynchronous operations that can tolerate higher latency. + * By isolating its resources from user-facing services, failures or slowdowns in background + * processing don't impact critical user operations. * - *

- * Example use cases: + *

Example use cases: * *

*/ public class BackgroundService extends BulkheadService { - private static final int DEFAULT_QUEUE_CAPACITY = 20; + private static final int DEFAULT_QUEUE_CAPACITY = 20; - /** - * Creates a BackgroundService with specified thread pool size. - * - * @param maxThreads maximum number of threads for background processing - */ - public BackgroundService(int maxThreads) { - super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); - } + /** + * Creates a BackgroundService with specified thread pool size. + * + * @param maxThreads maximum number of threads for background processing + */ + public BackgroundService(int maxThreads) { + super("BackgroundService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } - @Override - protected void handleRejectedTask(Task task) { - super.handleRejectedTask(task); - } + @Override + protected void handleRejectedTask(Task task) { + super.handleRejectedTask(task); + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java index 9124e8a0ca5f..4be9aeafacf0 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/BulkheadService.java @@ -1,148 +1,159 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base class for services implementing the Bulkhead pattern. * - *

- * This class provides resource isolation by maintaining a dedicated thread pool - * for each service. When the thread pool is exhausted, new tasks are rejected - * quickly (fail-fast behavior) rather than blocking or consuming resources from - * other services. + *

This class provides resource isolation by maintaining a dedicated thread pool for each + * service. When the thread pool is exhausted, new tasks are rejected quickly (fail-fast behavior) + * rather than blocking or consuming resources from other services. * - *

- * Key features: + *

Key features: * *

*/ public abstract class BulkheadService { - private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BulkheadService.class); - protected final ThreadPoolExecutor executor; - protected final String serviceName; + protected final ThreadPoolExecutor executor; + protected final String serviceName; - /** - * Creates a new BulkheadService with specified thread pool configuration. - * - * @param serviceName name of the service for logging - * @param maxPoolSize maximum number of threads in the pool - * @param queueCapacity maximum number of tasks that can be queued - */ - protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { - this.serviceName = serviceName; - this.executor - = new ThreadPoolExecutor( - maxPoolSize, - maxPoolSize, - 60L, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(queueCapacity), - new ThreadPoolExecutor.AbortPolicy()); - LOGGER.info( - "Created {} with {} threads and queue capacity {}", - serviceName, - maxPoolSize, - queueCapacity); - } + /** + * Creates a new BulkheadService with specified thread pool configuration. + * + * @param serviceName name of the service for logging + * @param maxPoolSize maximum number of threads in the pool + * @param queueCapacity maximum number of tasks that can be queued + */ + protected BulkheadService(String serviceName, int maxPoolSize, int queueCapacity) { + this.serviceName = serviceName; + this.executor = + new ThreadPoolExecutor( + maxPoolSize, + maxPoolSize, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(queueCapacity), + new ThreadPoolExecutor.AbortPolicy()); + LOGGER.info( + "Created {} with {} threads and queue capacity {}", + serviceName, + maxPoolSize, + queueCapacity); + } - /** - * Submits a task for execution. Returns false if the task is rejected due - * to resource exhaustion. - * - * @param task the task to execute - * @return true if task was accepted, false if rejected - */ - public boolean submitTask(Task task) { - try { - executor.execute(() -> processTask(task)); - LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); - return true; - } catch (RejectedExecutionException e) { - LOGGER.warn( - "[{}] Task '{}' REJECTED - bulkhead is full (fail-fast)", serviceName, task.getName()); - handleRejectedTask(task); - return false; - } + /** + * Submits a task for execution. Returns false if the task is rejected due to resource exhaustion. + * + * @param task the task to execute + * @return true if task was accepted, false if rejected + */ + public boolean submitTask(Task task) { + try { + executor.execute(() -> processTask(task)); + LOGGER.info("[{}] Task '{}' submitted successfully", serviceName, task.getName()); + return true; + } catch (RejectedExecutionException e) { + LOGGER.warn( + "[{}] Task '{}' REJECTED - bulkhead is full (fail-fast)", serviceName, task.getName()); + handleRejectedTask(task); + return false; } + } - /** - * Processes the given task. Subclasses can override for custom behavior. - * - * @param task the task to process - */ - protected void processTask(Task task) { - LOGGER.info( - "[{}] Processing task '{}' (type: {}, duration: {}ms)", - serviceName, - task.getName(), - task.getType(), - task.getDurationMs()); - try { - Thread.sleep(task.getDurationMs()); - LOGGER.info("[{}] Task '{}' completed successfully", serviceName, task.getName()); - } catch (InterruptedException e) { - LOGGER.error("[{}] Task '{}' interrupted", serviceName, task.getName()); - Thread.currentThread().interrupt(); - } + /** + * Processes the given task. Subclasses can override for custom behavior. + * + * @param task the task to process + */ + protected void processTask(Task task) { + LOGGER.info( + "[{}] Processing task '{}' (type: {}, duration: {}ms)", + serviceName, + task.getName(), + task.getType(), + task.getDurationMs()); + try { + Thread.sleep(task.getDurationMs()); + LOGGER.info("[{}] Task '{}' completed successfully", serviceName, task.getName()); + } catch (InterruptedException e) { + LOGGER.error("[{}] Task '{}' interrupted", serviceName, task.getName()); + Thread.currentThread().interrupt(); } + } - /** - * Handles a rejected task. Subclasses can override for custom rejection - * handling. - * - * @param task the rejected task - */ - protected void handleRejectedTask(Task task) { - LOGGER.info( - "[{}] Rejected task '{}' - consider retry or fallback logic", - serviceName, - task.getName()); - } + /** + * Handles a rejected task. Subclasses can override for custom rejection handling. + * + * @param task the rejected task + */ + protected void handleRejectedTask(Task task) { + LOGGER.info( + "[{}] Rejected task '{}' - consider retry or fallback logic", serviceName, task.getName()); + } - /** - * Gets the number of currently active threads. - * - * @return number of active threads - */ - public int getActiveThreads() { - return executor.getActiveCount(); - } + /** + * Gets the number of currently active threads. + * + * @return number of active threads + */ + public int getActiveThreads() { + return executor.getActiveCount(); + } - /** - * Gets the current queue size. - * - * @return number of tasks in queue - */ - public int getQueueSize() { - return executor.getQueue().size(); - } + /** + * Gets the current queue size. + * + * @return number of tasks in queue + */ + public int getQueueSize() { + return executor.getQueue().size(); + } - /** - * Shuts down the thread pool gracefully. - */ - public void shutdown() { - LOGGER.info("[{}] Shutting down thread pool", serviceName); - executor.shutdown(); - try { - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - executor.shutdownNow(); - } - } catch (InterruptedException e) { - executor.shutdownNow(); - Thread.currentThread().interrupt(); - } + /** Shuts down the thread pool gracefully. */ + public void shutdown() { + LOGGER.info("[{}] Shutting down thread pool", serviceName); + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); } + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java index f3cd40ad6e74..d0a477c49114 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/Task.java @@ -1,3 +1,24 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; import lombok.Getter; @@ -6,21 +27,19 @@ /** * Represents a task to be executed by a bulkhead service. * - *

- * Tasks are characterized by their name, type, and expected duration. This - * information is useful for logging, monitoring, and demonstrating the bulkhead - * pattern's behavior under different loads. + *

Tasks are characterized by their name, type, and expected duration. This information is useful + * for logging, monitoring, and demonstrating the bulkhead pattern's behavior under different loads. */ @Getter @RequiredArgsConstructor public class Task { - private final String name; - private final TaskType type; - private final long durationMs; + private final String name; + private final TaskType type; + private final long durationMs; - @Override - public String toString() { - return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; - } + @Override + public String toString() { + return "Task{name='" + name + "', type=" + type + ", duration=" + durationMs + "ms}"; + } } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java index 8a6884b736d4..cc9b8a236fae 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/TaskType.java @@ -1,21 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; /** * Enumeration of task types in the system. * - *

- * Different task types may have different priorities, resource requirements, - * and SLA expectations. The bulkhead pattern allows us to isolate resources - * based on these types. + *

Different task types may have different priorities, resource requirements, and SLA + * expectations. The bulkhead pattern allows us to isolate resources based on these types. */ public enum TaskType { - /** - * User-facing requests that require low latency and high availability. - */ - USER_REQUEST, - /** - * Background processing tasks that can tolerate higher latency. - */ - BACKGROUND_PROCESSING + /** User-facing requests that require low latency and high availability. */ + USER_REQUEST, + /** Background processing tasks that can tolerate higher latency. */ + BACKGROUND_PROCESSING } diff --git a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java index 08b5e27a6152..29c32ccace71 100644 --- a/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java +++ b/microservices-bulkhead/src/main/java/com/iluwatar/bulkhead/UserService.java @@ -1,38 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK + * framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License Copyright © 2014-2025 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package com.iluwatar.bulkhead; /** * Service for handling user-facing requests with dedicated resources. * - *

- * This service represents critical user interactions that require high - * availability and fast response times. By isolating its resources using the - * Bulkhead pattern, it remains responsive even when other services (like - * background processing) are experiencing issues or heavy load. + *

This service represents critical user interactions that require high availability and fast + * response times. By isolating its resources using the Bulkhead pattern, it remains responsive even + * when other services (like background processing) are experiencing issues or heavy load. * - *

- * Example use cases: + *

Example use cases: * *

*/ public class UserService extends BulkheadService { - private static final int DEFAULT_QUEUE_CAPACITY = 10; + private static final int DEFAULT_QUEUE_CAPACITY = 10; - /** - * Creates a UserService with specified thread pool size. - * - * @param maxThreads maximum number of threads for handling user requests - */ - public UserService(int maxThreads) { - super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); - } + /** + * Creates a UserService with specified thread pool size. + * + * @param maxThreads maximum number of threads for handling user requests + */ + public UserService(int maxThreads) { + super("UserService", maxThreads, DEFAULT_QUEUE_CAPACITY); + } - @Override - protected void handleRejectedTask(Task task) { - super.handleRejectedTask(task); - } + @Override + protected void handleRejectedTask(Task task) { + super.handleRejectedTask(task); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java index bdea91e52de7..f795c7cceb2d 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/AppTest.java @@ -25,8 +25,8 @@ class AppTest { - @Test - void shouldRunWithoutException() throws Exception { - App.main(new String[]{}); - } + @Test + void shouldRunWithoutException() throws Exception { + App.main(new String[] {}); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java index dbd6690a073b..81ff54c1bcec 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BackgroundServiceTest.java @@ -22,53 +22,52 @@ package com.iluwatar.bulkhead; import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class BackgroundServiceTest { - private BackgroundService backgroundService; + private BackgroundService backgroundService; - @BeforeEach - void setUp() { - backgroundService = new BackgroundService(2); - } + @BeforeEach + void setUp() { + backgroundService = new BackgroundService(2); + } - @AfterEach - void tearDown() { - backgroundService.shutdown(); - } + @AfterEach + void tearDown() { + backgroundService.shutdown(); + } - @Test - void shouldAcceptBackgroundTask() { - assertThat( - backgroundService.submitTask( - new Task("bg-job-1", TaskType.BACKGROUND_PROCESSING, 0))) - .isTrue(); - } + @Test + void shouldAcceptBackgroundTask() { + assertThat( + backgroundService.submitTask(new Task("bg-job-1", TaskType.BACKGROUND_PROCESSING, 0))) + .isTrue(); + } - @Test - void shouldRejectWhenBulkheadIsFull() { - for (int i = 0; i < 22; i++) { - backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 5000)); - } - assertThat( - backgroundService.submitTask( - new Task("overflow", TaskType.BACKGROUND_PROCESSING, 100))) - .isFalse(); + @Test + void shouldRejectWhenBulkheadIsFull() { + for (int i = 0; i < 22; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 5000)); } + assertThat( + backgroundService.submitTask(new Task("overflow", TaskType.BACKGROUND_PROCESSING, 100))) + .isFalse(); + } - @Test - void overloadShouldNotAffectUserService() { - var userService = new UserService(2); - try { - for (int i = 0; i < 25; i++) { - backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); - } - assertThat(userService.submitTask(new Task("user-req", TaskType.USER_REQUEST, 0))).isTrue(); - } finally { - userService.shutdown(); - } + @Test + void overloadShouldNotAffectUserService() { + var userService = new UserService(2); + try { + for (int i = 0; i < 25; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); + } + assertThat(userService.submitTask(new Task("user-req", TaskType.USER_REQUEST, 0))).isTrue(); + } finally { + userService.shutdown(); } + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java index 72704c4146dd..0ff365782ad9 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/BulkheadServiceTest.java @@ -22,58 +22,59 @@ package com.iluwatar.bulkhead; import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class BulkheadServiceTest { - private UserService service; + private UserService service; - @BeforeEach - void setUp() { - service = new UserService(2); - } + @BeforeEach + void setUp() { + service = new UserService(2); + } - @AfterEach - void tearDown() { - service.shutdown(); - } + @AfterEach + void tearDown() { + service.shutdown(); + } - @Test - void shouldAcceptTaskWhenCapacityAvailable() { - var task = new Task("task-1", TaskType.USER_REQUEST, 0); - assertThat(service.submitTask(task)).isTrue(); - } + @Test + void shouldAcceptTaskWhenCapacityAvailable() { + var task = new Task("task-1", TaskType.USER_REQUEST, 0); + assertThat(service.submitTask(task)).isTrue(); + } - @Test - void shouldRejectTaskWhenBulkheadIsFull() { - for (int i = 0; i < 12; i++) { - service.submitTask(new Task("blocker-" + i, TaskType.USER_REQUEST, 5000)); - } - assertThat(service.submitTask(new Task("overflow", TaskType.USER_REQUEST, 100))).isFalse(); + @Test + void shouldRejectTaskWhenBulkheadIsFull() { + for (int i = 0; i < 12; i++) { + service.submitTask(new Task("blocker-" + i, TaskType.USER_REQUEST, 5000)); } + assertThat(service.submitTask(new Task("overflow", TaskType.USER_REQUEST, 100))).isFalse(); + } - @Test - void shouldReportActiveThreadCount() throws InterruptedException { - service.submitTask(new Task("t1", TaskType.USER_REQUEST, 500)); - service.submitTask(new Task("t2", TaskType.USER_REQUEST, 500)); - Thread.sleep(100); - assertThat(service.getActiveThreads()).isGreaterThanOrEqualTo(0); - } + @Test + void shouldReportActiveThreadCount() throws InterruptedException { + service.submitTask(new Task("t1", TaskType.USER_REQUEST, 500)); + service.submitTask(new Task("t2", TaskType.USER_REQUEST, 500)); + Thread.sleep(100); + assertThat(service.getActiveThreads()).isGreaterThanOrEqualTo(0); + } - @Test - void shouldReportQueueSize() throws InterruptedException { - service.submitTask(new Task("t1", TaskType.USER_REQUEST, 2000)); - service.submitTask(new Task("t2", TaskType.USER_REQUEST, 2000)); - service.submitTask(new Task("queued", TaskType.USER_REQUEST, 2000)); - Thread.sleep(100); - assertThat(service.getQueueSize()).isGreaterThanOrEqualTo(0); - } + @Test + void shouldReportQueueSize() throws InterruptedException { + service.submitTask(new Task("t1", TaskType.USER_REQUEST, 2000)); + service.submitTask(new Task("t2", TaskType.USER_REQUEST, 2000)); + service.submitTask(new Task("queued", TaskType.USER_REQUEST, 2000)); + Thread.sleep(100); + assertThat(service.getQueueSize()).isGreaterThanOrEqualTo(0); + } - @Test - void shutdownShouldCompleteWithoutError() { - service.submitTask(new Task("t", TaskType.USER_REQUEST, 0)); - service.shutdown(); - } + @Test + void shutdownShouldCompleteWithoutError() { + service.submitTask(new Task("t", TaskType.USER_REQUEST, 0)); + service.shutdown(); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java index 705e36590161..8a7238f529e0 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTest.java @@ -22,32 +22,33 @@ package com.iluwatar.bulkhead; import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; class TaskTest { - @Test - void shouldReturnCorrectName() { - var task = new Task("my-task", TaskType.USER_REQUEST, 100); - assertThat(task.getName()).isEqualTo("my-task"); - } + @Test + void shouldReturnCorrectName() { + var task = new Task("my-task", TaskType.USER_REQUEST, 100); + assertThat(task.getName()).isEqualTo("my-task"); + } - @Test - void shouldReturnCorrectType() { - var task = new Task("t", TaskType.BACKGROUND_PROCESSING, 50); - assertThat(task.getType()).isEqualTo(TaskType.BACKGROUND_PROCESSING); - } + @Test + void shouldReturnCorrectType() { + var task = new Task("t", TaskType.BACKGROUND_PROCESSING, 50); + assertThat(task.getType()).isEqualTo(TaskType.BACKGROUND_PROCESSING); + } - @Test - void shouldReturnCorrectDuration() { - var task = new Task("t", TaskType.USER_REQUEST, 250); - assertThat(task.getDurationMs()).isEqualTo(250); - } + @Test + void shouldReturnCorrectDuration() { + var task = new Task("t", TaskType.USER_REQUEST, 250); + assertThat(task.getDurationMs()).isEqualTo(250); + } - @Test - void toStringShouldContainNameTypeAndDuration() { - var task = new Task("my-task", TaskType.USER_REQUEST, 100); - var str = task.toString(); - assertThat(str).contains("my-task").contains("USER_REQUEST").contains("100"); - } + @Test + void toStringShouldContainNameTypeAndDuration() { + var task = new Task("my-task", TaskType.USER_REQUEST, 100); + var str = task.toString(); + assertThat(str).contains("my-task").contains("USER_REQUEST").contains("100"); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java index 53d8d540ab5f..13aaaa70459b 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/TaskTypeTest.java @@ -22,22 +22,23 @@ package com.iluwatar.bulkhead; import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; class TaskTypeTest { - @Test - void shouldHaveTwoValues() { - assertThat(TaskType.values()).hasSize(2); - } + @Test + void shouldHaveTwoValues() { + assertThat(TaskType.values()).hasSize(2); + } - @Test - void shouldContainUserRequest() { - assertThat(TaskType.valueOf("USER_REQUEST")).isEqualTo(TaskType.USER_REQUEST); - } + @Test + void shouldContainUserRequest() { + assertThat(TaskType.valueOf("USER_REQUEST")).isEqualTo(TaskType.USER_REQUEST); + } - @Test - void shouldContainBackgroundProcessing() { - assertThat(TaskType.valueOf("BACKGROUND_PROCESSING")).isEqualTo(TaskType.BACKGROUND_PROCESSING); - } + @Test + void shouldContainBackgroundProcessing() { + assertThat(TaskType.valueOf("BACKGROUND_PROCESSING")).isEqualTo(TaskType.BACKGROUND_PROCESSING); + } } diff --git a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java index 1d78fbae4773..43d7068d1fba 100644 --- a/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java +++ b/microservices-bulkhead/src/test/java/com/iluwatar/bulkhead/UserServiceTest.java @@ -22,39 +22,40 @@ package com.iluwatar.bulkhead; import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class UserServiceTest { - private UserService userService; + private UserService userService; - @BeforeEach - void setUp() { - userService = new UserService(2); - } + @BeforeEach + void setUp() { + userService = new UserService(2); + } - @AfterEach - void tearDown() { - userService.shutdown(); - } + @AfterEach + void tearDown() { + userService.shutdown(); + } - @Test - void shouldAcceptUserRequestTask() { - assertThat(userService.submitTask(new Task("user-req-1", TaskType.USER_REQUEST, 0))).isTrue(); - } + @Test + void shouldAcceptUserRequestTask() { + assertThat(userService.submitTask(new Task("user-req-1", TaskType.USER_REQUEST, 0))).isTrue(); + } - @Test - void shouldIsolateFromOverloadedOtherService() { - var backgroundService = new BackgroundService(1); - try { - for (int i = 0; i < 25; i++) { - backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); - } - assertThat(userService.submitTask(new Task("critical", TaskType.USER_REQUEST, 0))).isTrue(); - } finally { - backgroundService.shutdown(); - } + @Test + void shouldIsolateFromOverloadedOtherService() { + var backgroundService = new BackgroundService(1); + try { + for (int i = 0; i < 25; i++) { + backgroundService.submitTask(new Task("bg-" + i, TaskType.BACKGROUND_PROCESSING, 3000)); + } + assertThat(userService.submitTask(new Task("critical", TaskType.USER_REQUEST, 0))).isTrue(); + } finally { + backgroundService.shutdown(); } + } }