Skip to main content

Overview

ConfigurableService extends the Service class to support services that can have multiple deployment configurations. It provides a typed configuration object that can be customized before service initialization.
It’s not recommended to use ConfigurableService with ReusableOpenshiftDeployable, as different configurations in the same test run may cause deployment conflicts.

Class signature

public abstract class ConfigurableService<A extends Account, C, V extends Validation, S extends ServiceConfiguration> 
    extends Service<A, C, V>
Package: software.tnb.common.service Type parameters:
  • A - Account type that extends Account
  • C - Client type (service-specific)
  • V - Validation type that extends Validation
  • S - Service configuration type that extends ServiceConfiguration

Properties

configuration
S
The configuration instance for this service, automatically instantiated based on the generic type parameter
Inherits all properties from Service:
  • account - The account instance
  • client - The client instance
  • validation - The validation instance

Methods

getConfiguration()

Returns the configuration instance for this service.
public S getConfiguration()
return
S
The ServiceConfiguration instance for customizing service behavior
Example:
AWSService aws = ServiceFactory.create(AWSService.class);
aws.getConfiguration().useLocalstack(true);
aws.getConfiguration().setRegion("us-west-2");

defaultConfiguration()

Abstract method that must be implemented to define default configuration values. Called automatically by ServiceFactory after instantiation.
protected abstract void defaultConfiguration()
Example:
@Override
protected void defaultConfiguration() {
    getConfiguration().useLocalstack(false);
    getConfiguration().setRegion("us-east-1");
}

account()

Overrides the Service method to use ConfigurableService’s generic type resolution.
public A account()
return
A
The Account instance for this service

Implementation example

Here’s how to implement a ConfigurableService:
public abstract class AWSService<A extends AWSAccount, C extends SdkClient, V extends Validation>
    extends ConfigurableService<A, C, V, AWSConfiguration> {
    
    protected LocalStack localStack;
    
    @Override
    protected void defaultConfiguration() {
        getConfiguration().useLocalstack(false);
    }
    
    @Override
    protected C client() {
        if (client == null) {
            Class<C> clientClass = (Class<C>) ReflectionUtil
                .getGenericTypesOf(AWSService.class, this.getClass())[1];
            client = AWSClient.createDefaultClient(
                account(), 
                clientClass, 
                getConfiguration().isLocalstack() ? localStack.clientUrl() : null
            );
        }
        return client;
    }
    
    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        if (getConfiguration().isLocalstack()) {
            localStack = ServiceFactory.create(LocalStack.class);
            localStack.beforeAll(extensionContext);
        }
    }
    
    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        if (client != null) {
            client.close();
            client = null;
        }
        if (localStack != null) {
            localStack.afterAll(extensionContext);
        }
    }
}

Configuration patterns

// Service uses default configuration
SQS sqs = ServiceFactory.create(SQS.class);
// defaultConfiguration() was already called

Configuration classes

Service configurations extend ServiceConfiguration:
public class AWSConfiguration extends ServiceConfiguration {
    private boolean useLocalstack;
    private String region;
    
    public boolean isLocalstack() {
        return useLocalstack;
    }
    
    public void useLocalstack(boolean useLocalstack) {
        this.useLocalstack = useLocalstack;
    }
    
    public String getRegion() {
        return region;
    }
    
    public void setRegion(String region) {
        this.region = region;
    }
}

Common configurable services

Many TNB services extend ConfigurableService:
  • AWS services - Configure region, localstack usage
  • Database services - Configure connection pools, schemas
  • Message brokers - Configure topics, queues, clusters
  • HTTP services - Configure ports, routes, authentication

Configuration lifecycle

  1. Service creation - ConfigurableService instantiated
  2. Configuration instantiation - Configuration object created via reflection
  3. Default configuration - defaultConfiguration() called by ServiceFactory
  4. Custom configuration - User’s configuration Consumer applied (if provided)
  5. Service initialization - beforeAll() uses configuration values
Configuration must be set before calling beforeAll(). Changes after initialization may not take effect.

Best practices

Do

  • Define sensible defaults in defaultConfiguration()
  • Make configuration immutable after beforeAll()
  • Validate configuration values in beforeAll()
  • Document configuration options clearly

Don’t

  • Don’t modify configuration after service initialization
  • Don’t use ConfigurableService with ReusableOpenshiftDeployable
  • Don’t skip defaultConfiguration() implementation
  • Don’t make configuration changes thread-unsafe

Build docs developers (and LLMs) love