SynchronousBoundaryResilienceProperties.java

package com.ecommerce.trade.infrastructure.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

import java.time.Duration;

@ConfigurationProperties("ecommerce.trade.client.synchronous-boundary")
public record SynchronousBoundaryResilienceProperties(
        @DefaultValue("5s") Duration totalBudget,
        @DefaultValue("2") int queryMaxAttempts,
        @DefaultValue("100ms") Duration retryWait,
        @DefaultValue("16") int queryMaxConcurrentCalls,
        @DefaultValue("8") int commandMaxConcurrentCalls,
        @DefaultValue("0ms") Duration bulkheadMaxWait,
        @DefaultValue("10") int slidingWindowSize,
        @DefaultValue("5") int minimumNumberOfCalls,
        @DefaultValue("50") float failureRateThreshold,
        @DefaultValue("10s") Duration openStateWait,
        @DefaultValue("2") int halfOpenCalls
) {

    public SynchronousBoundaryResilienceProperties {
        requirePositive(totalBudget, "total-budget");
        requireNonNegative(retryWait, "retry-wait");
        requireNonNegative(bulkheadMaxWait, "bulkhead-max-wait");
        requirePositive(openStateWait, "open-state-wait");
        if (queryMaxAttempts < 1 || queryMaxAttempts > 3) {
            throw invalid("query-max-attempts must be between 1 and 3");
        }
        requireConcurrency(queryMaxConcurrentCalls, "query-max-concurrent-calls");
        requireConcurrency(commandMaxConcurrentCalls, "command-max-concurrent-calls");
        if (slidingWindowSize < 2 || slidingWindowSize > 100) {
            throw invalid("sliding-window-size must be between 2 and 100");
        }
        if (minimumNumberOfCalls < 1 || minimumNumberOfCalls > slidingWindowSize) {
            throw invalid("minimum-number-of-calls must be between 1 and sliding-window-size");
        }
        if (failureRateThreshold <= 0 || failureRateThreshold > 100) {
            throw invalid("failure-rate-threshold must be greater than 0 and at most 100");
        }
        if (halfOpenCalls < 1 || halfOpenCalls > 10) {
            throw invalid("half-open-calls must be between 1 and 10");
        }
    }

    private static void requireConcurrency(int value, String property) {
        if (value < 1 || value > 128) {
            throw invalid(property + " must be between 1 and 128");
        }
    }

    private static void requirePositive(Duration value, String property) {
        if (value == null || value.isZero() || value.isNegative()) {
            throw invalid(property + " must be positive");
        }
    }

    private static void requireNonNegative(Duration value, String property) {
        if (value == null || value.isNegative()) {
            throw invalid(property + " must not be negative");
        }
    }

    private static IllegalArgumentException invalid(String message) {
        return new IllegalArgumentException(
                "ecommerce.trade.client.synchronous-boundary." + message);
    }
}