Custom Pattern Implementations in Java

Overview

Custom patterns are reusable solutions to common problems that aren't covered by traditional Gang of Four patterns. These patterns address modern challenges in software development.

1. Repository Pattern with Caching

Generic Repository Interface

public interface Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
T save(T entity);
void deleteById(ID id);
boolean existsById(ID id);
long count();
}

Cached Repository Implementation

public class CachedRepository<T, ID> implements Repository<T, ID> {
private final Repository<T, ID> delegate;
private final Cache<ID, T> cache;
private final Cache<String, List<T>> queryCache;
public CachedRepository(Repository<T, ID> delegate, 
CacheConfig cacheConfig) {
this.delegate = delegate;
this.cache = Caffeine.newBuilder()
.maximumSize(cacheConfig.getMaxSize())
.expireAfterWrite(cacheConfig.getTtl())
.build();
this.queryCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
@Override
public Optional<T> findById(ID id) {
T cached = cache.getIfPresent(id);
if (cached != null) {
return Optional.of(cached);
}
Optional<T> result = delegate.findById(id);
result.ifPresent(entity -> cache.put(id, entity));
return result;
}
@Override
public List<T> findAll() {
String cacheKey = "findAll";
List<T> cached = queryCache.getIfPresent(cacheKey);
if (cached != null) {
return cached;
}
List<T> result = delegate.findAll();
queryCache.put(cacheKey, result);
return result;
}
@Override
public T save(T entity) {
T saved = delegate.save(entity);
// Invalidate relevant caches
cache.invalidateAll();
queryCache.invalidateAll();
return saved;
}
@Override
public void deleteById(ID id) {
delegate.deleteById(id);
cache.invalidate(id);
queryCache.invalidateAll();
}
// Cache management methods
public void evictById(ID id) {
cache.invalidate(id);
}
public void evictAll() {
cache.invalidateAll();
queryCache.invalidateAll();
}
}

2. Pipeline Pattern

Pipeline Interface

public interface Pipeline<T> {
T execute(T input);
Pipeline<T> addStep(PipelineStep<T> step);
Pipeline<T> addConditionalStep(Predicate<T> condition, PipelineStep<T> step);
}

Pipeline Implementation

public class ProcessingPipeline<T> implements Pipeline<T> {
private final List<PipelineStep<T>> steps;
private final List<ConditionalStep<T>> conditionalSteps;
public ProcessingPipeline() {
this.steps = new ArrayList<>();
this.conditionalSteps = new ArrayList<>();
}
@Override
public T execute(T input) {
T current = input;
// Execute regular steps
for (PipelineStep<T> step : steps) {
current = step.execute(current);
}
// Execute conditional steps
for (ConditionalStep<T> conditional : conditionalSteps) {
if (conditional.getCondition().test(current)) {
current = conditional.getStep().execute(current);
}
}
return current;
}
@Override
public Pipeline<T> addStep(PipelineStep<T> step) {
steps.add(step);
return this;
}
@Override
public Pipeline<T> addConditionalStep(Predicate<T> condition, PipelineStep<T> step) {
conditionalSteps.add(new ConditionalStep<>(condition, step));
return this;
}
// Builder for fluent pipeline creation
public static <T> Builder<T> builder() {
return new Builder<>();
}
public static class Builder<T> {
private final ProcessingPipeline<T> pipeline;
private Builder() {
this.pipeline = new ProcessingPipeline<>();
}
public Builder<T> withStep(PipelineStep<T> step) {
pipeline.addStep(step);
return this;
}
public Builder<T> withConditionalStep(Predicate<T> condition, PipelineStep<T> step) {
pipeline.addConditionalStep(condition, step);
return this;
}
public ProcessingPipeline<T> build() {
return pipeline;
}
}
}

Pipeline Steps

@FunctionalInterface
public interface PipelineStep<T> {
T execute(T input);
default PipelineStep<T> andThen(PipelineStep<T> next) {
return input -> next.execute(this.execute(input));
}
}
// Concrete pipeline steps
public class ValidationStep<T> implements PipelineStep<T> {
private final Validator<T> validator;
public ValidationStep(Validator<T> validator) {
this.validator = validator;
}
@Override
public T execute(T input) {
ValidationResult result = validator.validate(input);
if (!result.isValid()) {
throw new ValidationException("Validation failed: " + result.getErrors());
}
return input;
}
}
public class TransformationStep<T> implements PipelineStep<T> {
private final Function<T, T> transformer;
public TransformationStep(Function<T, T> transformer) {
this.transformer = transformer;
}
@Override
public T execute(T input) {
return transformer.apply(input);
}
}
public class LoggingStep<T> implements PipelineStep<T> {
private final Logger logger;
private final String stepName;
public LoggingStep(Logger logger, String stepName) {
this.logger = logger;
this.stepName = stepName;
}
@Override
public T execute(T input) {
logger.info("Executing step: {} with input: {}", stepName, input);
T result = input; // Pass-through for logging
logger.info("Step {} completed with result: {}", stepName, result);
return result;
}
}

3. Specification Pattern

Specification Interface

public interface Specification<T> {
boolean isSatisfiedBy(T candidate);
default Specification<T> and(Specification<T> other) {
return new AndSpecification<>(this, other);
}
default Specification<T> or(Specification<T> other) {
return new OrSpecification<>(this, other);
}
default Specification<T> not() {
return new NotSpecification<>(this);
}
static <T> Specification<T> of(Predicate<T> predicate) {
return predicate::test;
}
}

Composite Specifications

public class AndSpecification<T> implements Specification<T> {
private final Specification<T> left;
private final Specification<T> right;
public AndSpecification(Specification<T> left, Specification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return left.isSatisfiedBy(candidate) && right.isSatisfiedBy(candidate);
}
}
public class OrSpecification<T> implements Specification<T> {
private final Specification<T> left;
private final Specification<T> right;
public OrSpecification(Specification<T> left, Specification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return left.isSatisfiedBy(candidate) || right.isSatisfiedBy(candidate);
}
}
public class NotSpecification<T> implements Specification<T> {
private final Specification<T> specification;
public NotSpecification(Specification<T> specification) {
this.specification = specification;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return !specification.isSatisfiedBy(candidate);
}
}

Concrete Specifications

public class UserSpecifications {
public static Specification<User> isActive() {
return candidate -> candidate.isActive();
}
public static Specification<User> hasRole(String role) {
return candidate -> candidate.getRoles().contains(role);
}
public static Specification<User> createdAfter(Instant date) {
return candidate -> candidate.getCreatedAt().isAfter(date);
}
public static Specification<User> withMinimumAge(int age) {
return candidate -> Period.between(candidate.getBirthDate(), LocalDate.now())
.getYears() >= age;
}
public static Specification<User> withEmailDomain(String domain) {
return candidate -> candidate.getEmail().endsWith("@" + domain);
}
}
// Usage example
public class UserService {
public List<User> findUsers(Specification<User> spec) {
return userRepository.findAll().stream()
.filter(spec::isSatisfiedBy)
.collect(Collectors.toList());
}
// Complex query example
public List<User> findEligibleAdmins() {
Specification<User> spec = UserSpecifications.isActive()
.and(UserSpecifications.hasRole("ADMIN"))
.and(UserSpecifications.withMinimumAge(18))
.and(UserSpecifications.createdAfter(Instant.now().minus(365, ChronoUnit.DAYS)));
return findUsers(spec);
}
}

4. Retry Pattern

Retry Policy

public class RetryPolicy {
private final int maxAttempts;
private final Duration initialDelay;
private final Duration maxDelay;
private final double backoffMultiplier;
private final Predicate<Exception> retryCondition;
private RetryPolicy(Builder builder) {
this.maxAttempts = builder.maxAttempts;
this.initialDelay = builder.initialDelay;
this.maxDelay = builder.maxDelay;
this.backoffMultiplier = builder.backoffMultiplier;
this.retryCondition = builder.retryCondition;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private int maxAttempts = 3;
private Duration initialDelay = Duration.ofSeconds(1);
private Duration maxDelay = Duration.ofMinutes(1);
private double backoffMultiplier = 2.0;
private Predicate<Exception> retryCondition = e -> true;
public Builder maxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
return this;
}
public Builder initialDelay(Duration initialDelay) {
this.initialDelay = initialDelay;
return this;
}
public Builder maxDelay(Duration maxDelay) {
this.maxDelay = maxDelay;
return this;
}
public Builder backoffMultiplier(double multiplier) {
this.backoffMultiplier = multiplier;
return this;
}
public Builder retryOn(Predicate<Exception> condition) {
this.retryCondition = condition;
return this;
}
public Builder retryOn(Class<? extends Exception> exceptionType) {
this.retryCondition = e -> exceptionType.isInstance(e);
return this;
}
public RetryPolicy build() {
return new RetryPolicy(this);
}
}
// Getters
public int getMaxAttempts() { return maxAttempts; }
public Duration getInitialDelay() { return initialDelay; }
public Duration getMaxDelay() { return maxDelay; }
public double getBackoffMultiplier() { return backoffMultiplier; }
public Predicate<Exception> getRetryCondition() { return retryCondition; }
}

Retry Template

public class RetryTemplate {
private final RetryPolicy retryPolicy;
private final ScheduledExecutorService scheduler;
public RetryTemplate(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
}
public <T> T execute(Supplier<T> operation) {
Exception lastException = null;
for (int attempt = 1; attempt <= retryPolicy.getMaxAttempts(); attempt++) {
try {
return operation.get();
} catch (Exception e) {
lastException = e;
if (!shouldRetry(e) || attempt == retryPolicy.getMaxAttempts()) {
break;
}
Duration delay = calculateDelay(attempt);
sleep(delay);
}
}
throw new RetryExhaustedException("Operation failed after " + 
retryPolicy.getMaxAttempts() + " attempts", lastException);
}
public void execute(Runnable operation) {
execute(() -> {
operation.run();
return null;
});
}
public <T> CompletableFuture<T> executeAsync(Supplier<T> operation) {
return CompletableFuture.supplyAsync(() -> execute(operation));
}
private boolean shouldRetry(Exception e) {
return retryPolicy.getRetryCondition().test(e);
}
private Duration calculateDelay(int attempt) {
long delayMillis = (long) (retryPolicy.getInitialDelay().toMillis() * 
Math.pow(retryPolicy.getBackoffMultiplier(), attempt - 1));
return Duration.ofMillis(Math.min(delayMillis, retryPolicy.getMaxDelay().toMillis()));
}
private void sleep(Duration duration) {
try {
Thread.sleep(duration.toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RetryInterruptedException("Retry was interrupted", e);
}
}
}

5. Circuit Breaker Pattern

Circuit Breaker State

public enum CircuitBreakerState {
CLOSED,    // Normal operation
OPEN,      // Circuit is open, requests fail fast
HALF_OPEN  // Testing if service has recovered
}

Circuit Breaker Implementation

public class CircuitBreaker {
private final String name;
private final int failureThreshold;
private final Duration timeout;
private final Duration resetTimeout;
private final Clock clock;
private volatile CircuitBreakerState state = CircuitBreakerState.CLOSED;
private volatile int failureCount = 0;
private volatile Instant lastFailureTime;
private volatile Instant halfOpenTime;
public CircuitBreaker(String name, int failureThreshold, 
Duration timeout, Duration resetTimeout) {
this(name, failureThreshold, timeout, resetTimeout, Clock.systemUTC());
}
public CircuitBreaker(String name, int failureThreshold,
Duration timeout, Duration resetTimeout, Clock clock) {
this.name = name;
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.resetTimeout = resetTimeout;
this.clock = clock;
}
public <T> T execute(Supplier<T> supplier) {
if (!allowRequest()) {
throw new CircuitBreakerOpenException("Circuit breaker is open: " + name);
}
try {
T result = supplier.get();
onSuccess();
return result;
} catch (Exception e) {
onFailure();
throw e;
}
}
public void execute(Runnable runnable) {
execute(() -> {
runnable.run();
return null;
});
}
private boolean allowRequest() {
switch (state) {
case CLOSED:
return true;
case OPEN:
// Check if reset timeout has elapsed
if (lastFailureTime.plus(resetTimeout).isBefore(clock.instant())) {
state = CircuitBreakerState.HALF_OPEN;
halfOpenTime = clock.instant();
return true;
}
return false;
case HALF_OPEN:
// Allow limited requests in half-open state
if (halfOpenTime.plus(timeout).isBefore(clock.instant())) {
// Too much time passed, go back to open
state = CircuitBreakerState.OPEN;
return false;
}
return true;
default:
return false;
}
}
private void onSuccess() {
switch (state) {
case HALF_OPEN:
// Successful request in half-open state, reset circuit
reset();
break;
case CLOSED:
// Reset failure count on consecutive successes
if (failureCount > 0) {
failureCount = 0;
}
break;
}
}
private void onFailure() {
failureCount++;
lastFailureTime = clock.instant();
switch (state) {
case CLOSED:
if (failureCount >= failureThreshold) {
state = CircuitBreakerState.OPEN;
}
break;
case HALF_OPEN:
// Failure in half-open state, go back to open
state = CircuitBreakerState.OPEN;
break;
}
}
private void reset() {
state = CircuitBreakerState.CLOSED;
failureCount = 0;
lastFailureTime = null;
halfOpenTime = null;
}
// Getters for monitoring
public CircuitBreakerState getState() { return state; }
public int getFailureCount() { return failureCount; }
public String getName() { return name; }
public Instant getLastFailureTime() { return lastFailureTime; }
}

Circuit Breaker Registry

@Component
public class CircuitBreakerRegistry {
private final Map<String, CircuitBreaker> breakers;
private final CircuitBreakerConfig defaultConfig;
public CircuitBreakerRegistry(CircuitBreakerConfig defaultConfig) {
this.breakers = new ConcurrentHashMap<>();
this.defaultConfig = defaultConfig;
}
public CircuitBreaker getBreaker(String name) {
return breakers.computeIfAbsent(name, this::createCircuitBreaker);
}
public CircuitBreaker getBreaker(String name, CircuitBreakerConfig config) {
return breakers.computeIfAbsent(name, k -> createCircuitBreaker(k, config));
}
public Map<String, CircuitBreaker> getBreakers() {
return Collections.unmodifiableMap(breakers);
}
public CircuitBreakerStats getStats(String name) {
CircuitBreaker breaker = breakers.get(name);
if (breaker == null) {
return null;
}
return new CircuitBreakerStats(
breaker.getName(),
breaker.getState(),
breaker.getFailureCount(),
breaker.getLastFailureTime()
);
}
private CircuitBreaker createCircuitBreaker(String name) {
return createCircuitBreaker(name, defaultConfig);
}
private CircuitBreaker createCircuitBreaker(String name, CircuitBreakerConfig config) {
return new CircuitBreaker(
name,
config.getFailureThreshold(),
config.getTimeout(),
config.getResetTimeout(),
config.getClock()
);
}
}

6. Event Sourcing Pattern

Event Interface

public interface DomainEvent {
String getAggregateId();
Instant getTimestamp();
String getType();
int getVersion();
}
public abstract class BaseEvent implements DomainEvent {
private final String aggregateId;
private final Instant timestamp;
private final int version;
protected BaseEvent(String aggregateId, int version) {
this.aggregateId = aggregateId;
this.timestamp = Instant.now();
this.version = version;
}
@Override public String getAggregateId() { return aggregateId; }
@Override public Instant getTimestamp() { return timestamp; }
@Override public int getVersion() { return version; }
}

Aggregate Root

public abstract class AggregateRoot {
private final String id;
private int version = 0;
private final List<DomainEvent> changes = new ArrayList<>();
protected AggregateRoot(String id) {
this.id = id;
}
public String getId() { return id; }
public int getVersion() { return version; }
public List<DomainEvent> getUncommittedChanges() { 
return Collections.unmodifiableList(changes); 
}
public void markChangesAsCommitted() {
changes.clear();
}
public void loadFromHistory(List<DomainEvent> history) {
for (DomainEvent event : history) {
applyChange(event, false);
version = event.getVersion();
}
}
protected void applyChange(DomainEvent event) {
applyChange(event, true);
}
private void applyChange(DomainEvent event, boolean isNew) {
try {
Method method = getClass().getDeclaredMethod("apply", event.getClass());
method.setAccessible(true);
method.invoke(this, event);
} catch (Exception e) {
throw new RuntimeException("Error applying event", e);
}
if (isNew) {
changes.add(event);
}
}
}

Event Store

public interface EventStore {
void saveEvents(String aggregateId, List<DomainEvent> events, int expectedVersion);
List<DomainEvent> getEvents(String aggregateId);
List<DomainEvent> getEventsAfter(String aggregateId, int version);
List<String> getAggregateIds();
}
public class InMemoryEventStore implements EventStore {
private final Map<String, List<DomainEvent>> eventStore = new ConcurrentHashMap<>();
private final Map<String, Integer> currentVersions = new ConcurrentHashMap<>();
@Override
public void saveEvents(String aggregateId, List<DomainEvent> events, int expectedVersion) {
synchronized (aggregateId.intern()) {
int currentVersion = currentVersions.getOrDefault(aggregateId, -1);
if (currentVersion != expectedVersion) {
throw new ConcurrencyException("Concurrent modification detected for aggregate: " + aggregateId);
}
List<DomainEvent> aggregateEvents = eventStore.computeIfAbsent(aggregateId, k -> new ArrayList<>());
int version = expectedVersion;
for (DomainEvent event : events) {
version++;
// Create new event with correct version
aggregateEvents.add(event);
}
currentVersions.put(aggregateId, version);
}
}
@Override
public List<DomainEvent> getEvents(String aggregateId) {
return eventStore.getOrDefault(aggregateId, Collections.emptyList());
}
@Override
public List<DomainEvent> getEventsAfter(String aggregateId, int version) {
List<DomainEvent> allEvents = getEvents(aggregateId);
return allEvents.stream()
.filter(event -> event.getVersion() > version)
.collect(Collectors.toList());
}
@Override
public List<String> getAggregateIds() {
return new ArrayList<>(eventStore.keySet());
}
}

7. Builder Pattern with Fluent Interface

Advanced Builder Pattern

public class HttpRequest {
private final String url;
private final HttpMethod method;
private final Map<String, String> headers;
private final String body;
private final Duration timeout;
private final int maxRetries;
private final boolean followRedirects;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = Collections.unmodifiableMap(new HashMap<>(builder.headers));
this.body = builder.body;
this.timeout = builder.timeout;
this.maxRetries = builder.maxRetries;
this.followRedirects = builder.followRedirects;
}
public static Builder builder() {
return new Builder();
}
// Getters...
public static class Builder {
private String url;
private HttpMethod method = HttpMethod.GET;
private Map<String, String> headers = new HashMap<>();
private String body;
private Duration timeout = Duration.ofSeconds(30);
private int maxRetries = 3;
private boolean followRedirects = true;
public Builder url(String url) {
this.url = Objects.requireNonNull(url, "URL cannot be null");
return this;
}
public Builder method(HttpMethod method) {
this.method = Objects.requireNonNull(method, "Method cannot be null");
return this;
}
public Builder header(String name, String value) {
this.headers.put(Objects.requireNonNull(name), Objects.requireNonNull(value));
return this;
}
public Builder headers(Map<String, String> headers) {
this.headers.putAll(Objects.requireNonNull(headers));
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder jsonBody(Object object) {
try {
ObjectMapper mapper = new ObjectMapper();
this.body = mapper.writeValueAsString(object);
header("Content-Type", "application/json");
return this;
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize object to JSON", e);
}
}
public Builder timeout(Duration timeout) {
this.timeout = Objects.requireNonNull(timeout);
return this;
}
public Builder timeout(long amount, TimeUnit unit) {
return timeout(Duration.ofMillis(unit.toMillis(amount)));
}
public Builder maxRetries(int maxRetries) {
if (maxRetries < 0) {
throw new IllegalArgumentException("Max retries cannot be negative");
}
this.maxRetries = maxRetries;
return this;
}
public Builder followRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
return this;
}
public HttpRequest build() {
Objects.requireNonNull(url, "URL must be specified");
if (method == HttpMethod.POST || method == HttpMethod.PUT) {
if (body == null) {
throw new IllegalStateException("Body must be specified for " + method + " requests");
}
}
return new HttpRequest(this);
}
}
}

Usage Examples

Pipeline Pattern Usage

public class DataProcessingExample {
public static void main(String[] args) {
Pipeline<String> pipeline = ProcessingPipeline.<String>builder()
.withStep(new ValidationStep<>(new DataValidator()))
.withStep(new TransformationStep<>(String::toUpperCase))
.withStep(new LoggingStep<>(LoggerFactory.getLogger(DataProcessingExample.class), "uppercase"))
.withStep(new TransformationStep<>(s -> s.replaceAll("\\s+", "_")))
.withConditionalStep(s -> s.length() > 10, new TransformationStep<>(s -> s.substring(0, 10)))
.build();
String result = pipeline.execute("hello world");
System.out.println("Result: " + result); // "HELLO_WORL"
}
}

Circuit Breaker Usage

public class ExternalServiceClient {
private final CircuitBreaker circuitBreaker;
private final RestTemplate restTemplate;
public ExternalServiceClient(CircuitBreakerRegistry registry) {
this.circuitBreaker = registry.getBreaker("external-service",
CircuitBreakerConfig.builder()
.failureThreshold(5)
.resetTimeout(Duration.ofMinutes(1))
.build());
this.restTemplate = new RestTemplate();
}
public String callExternalService() {
return circuitBreaker.execute(() -> {
ResponseEntity<String> response = restTemplate.getForEntity(
"https://api.example.com/data", String.class);
return response.getBody();
});
}
}

These custom patterns provide robust, reusable solutions for common challenges in modern Java applications, including resilience, data processing, domain modeling, and API design.

Leave a Reply

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


Macro Nepal Helper