Skip to main content

Overview

Accounts in TNB are Java objects that contain all the information required to connect to a service, including credentials, endpoints, and configuration parameters.

The Account interface

The Account interface is minimal, providing utility methods for account manipulation:
public interface Account {
    /**
     * Create a Properties instance from this account.
     *
     * @return properties instance
     */
    default Properties toProperties() {
        Properties properties = new Properties();
        for (Field field : ReflectionUtil.getAllFields(new ArrayList<>(), this.getClass())) {
            try {
                field.setAccessible(true);
                // Null values can't be stored in properties
                if (field.get(this) != null) {
                    properties.put(StringUtils.replaceUnderscoreWithCamelCase(field.getName()), field.get(this));
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Unable to get field " + field.getName() + " value: ", e);
            }
        }
        return properties;
    }
}
The toProperties() method automatically converts all account fields into a Java Properties object, useful for configuring clients.

Account types

For self-hosted services, account values are typically hardcoded since the service is deployed by TNB.

Example: KafkaAccount

public class KafkaAccount implements Account {
    private String basicUser = "testuser";
    private String basicPassword = "testpassword";
    private String trustStore = "target/kafka-truststore.p12";
    private String trustStorePassword;
    
    public String basicUser() {
        return "testuser";
    }
    
    public String basicPassword() {
        return "testpassword";
    }
    
    public String trustStore() {
        return trustStore;
    }
    
    public String trustStorePassword() {
        return trustStorePassword;
    }
    
    public void setTrustStorePassword(String password) {
        this.trustStorePassword = password;
    }
}
Self-hosted service accounts use default credentials since TNB controls the deployment.

Creating accounts with AccountFactory

The AccountFactory class creates account instances and populates them with credentials:
T instance = AccountFactory.create(YourAccount.class);

How AccountFactory works

From AccountFactory.java:35-51:
public static <T extends Account> T create(Class<T> accountClass) {
    T instance = createInstance(accountClass);
    if (instance instanceof WithId) {
        LOG.debug("Loading {} account", accountClass.getSimpleName());
        if (loader == null) {
            try {
                loader = defaultLoader();
            } catch (Exception e) {
                fail("Could not load credentials", e);
            }
        }
        return loader.get(getCredentialsIds(instance), accountClass);
    } else {
        LOG.debug("Initialization of {}. No credentials loading needed.", accountClass.getSimpleName());
        return instance;
    }
}
1

Create instance

Instantiate the account class using reflection
2

Check for WithId

If the account implements WithId, proceed with credential loading
3

Initialize loader

Create a credentials loader based on configuration
4

Load credentials

Populate account fields from the credentials source

Credential loading strategies

TNB supports multiple credential sources, checked in the following order:

Overriding account IDs

You can override the default credentials ID for any account using system properties:
tnb.<accountClassName>.id=custom-id

Example

For an AWSAccount class with a default ID of “aws”:
# Use credentials from "aws-production" instead of "aws"
tnb.awsaccount.id=aws-production
The property name is the account class name in lowercase.

Composite accounts

Accounts can extend other accounts and inherit credentials, enabling a credential extension mechanism.

How it works

When creating an account instance, AccountFactory checks all parent classes for the WithId interface and loads credentials from all IDs.

Example

public class ParentAccount implements Account, WithId {
    private String parentKey;
    
    @Override
    public String credentialsId() {
        return "parent";
    }
}

public class ChildAccount extends ParentAccount {
    private String childKey;
    
    @Override
    public String credentialsId() {
        return "child";
    }
}

Credentials file

services:
    parent:
        credentials:
            parentKey: parentValue
    child:
        credentials:
            childKey: childValue
When you create a ChildAccount:
ChildAccount account = AccountFactory.create(ChildAccount.class);
The resulting account has both parentKey and childKey populated.
Credentials are merged in order from parent to child, with child values overriding parent values if there are conflicts.

Partial composite accounts

At least one of the credentials IDs must exist. This allows for flexible credential structures:
services:
    child:
        credentials:
            parentKey: parentValue
            childKey: childValue
This is valid even though the “parent” entry doesn’t exist, as long as all required fields are in the “child” entry.

Best practices

Never commit secrets

Use external credential sources (Vault or YAML files) and add them to .gitignore.

Use environment-specific IDs

Override account IDs per environment (dev, staging, production) using system properties.

Implement WithId for remote services

Always implement WithId for accounts that need external credentials.

Use composite accounts for reuse

Share common credentials across multiple accounts using inheritance.

Next steps

Services

Learn about the Service abstraction

Validation

Understand validation classes

Build docs developers (and LLMs) love