Skip to main content

Configuration


Module: Lens Version: 1.0.0-RELEASE Last Updated: October 25, 2025


Overview

This document describes all configuration aspects of the AWS Lens module:

  • Application configuration files
  • Database connections
  • External service integrations
  • Environment-specific settings
  • Security configurations
  • Performance tuning parameters

Configuration Files Structure

lens/
└── src/main/resources/
├── bootstrap.yml # Bootstrap config (Config Server)
├── application.yml # Main application config
├── application-dev.yml # Development environment
├── application-uat.yml # UAT environment
├── application-prod.yml # Production environment
├── logback.xml # Logging configuration
└── queries/ # SQL query files
├── cost/
│ ├── query.cost.summary.sql
│ └── query.cost.detail.sql
└── reservation/
└── query.ri.utilization.sql

1. Bootstrap Configuration

File: src/main/resources/bootstrap.yml

spring:
application:
name: lens

# Spring Cloud Config Server
cloud:
config:
uri: http://cloudonomic-spring-config.uat.cloudonomic.net
name: lens
label: prod
fail-fast: true
retry:
max-attempts: 6
initial-interval: 1000
multiplier: 1.1
max-interval: 2000

profiles:
active: ${ENV:dev} # Default to dev, override with ENV variable

Key Points:

  • Bootstrap config loads before application.yml
  • Config Server fetches environment-specific properties
  • fail-fast: true → application won't start if Config Server unavailable
  • Retry logic ensures resilience during Config Server startup

2. Main Application Configuration

File: src/main/resources/application.yml

Server Configuration

server:
port: 8080
servlet:
context-path: /lens
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
tomcat:
max-threads: 200
min-spare-threads: 10
accept-count: 100

Spring Boot Configuration

spring:
application:
name: lens

# Jackson JSON Configuration
jackson:
serialization:
write-dates-as-timestamps: false
indent-output: true
deserialization:
fail-on-unknown-properties: false
default-property-inclusion: non_null
time-zone: UTC

# MVC Configuration
mvc:
throw-exception-if-no-handler-found: true

# Resource Handling
web:
resources:
add-mappings: false

3. Database Configurations

3.1 Snowflake Configuration

snowflake:
url: jdbc:snowflake://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com
username: ${SNOWFLAKE_USER}
password: ${SNOWFLAKE_PASSWORD}
warehouse: LENS_WH
database: COST_DB
schema: PUBLIC
role: LENS_ROLE

# Connection Pool (HikariCP)
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000 # 30 seconds
idle-timeout: 600000 # 10 minutes
max-lifetime: 1800000 # 30 minutes
leak-detection-threshold: 60000 # 1 minute

# Query Configuration
query:
timeout: 300 # 5 minutes
fetch-size: 1000
use-result-cache: true

# Multi-tenant Schema Pattern
schema-pattern: CUSTOMER_{customerId}

Java Bean Configuration:

// SnowflakeConfiguration.java:45
@Configuration
public class SnowflakeConfiguration {

@Bean
@ConfigurationProperties(prefix = "snowflake.hikari")
public HikariConfig snowflakeHikariConfig() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(snowflakeUrl);
config.setUsername(snowflakeUser);
config.setPassword(snowflakePassword);
config.setDriverClassName("net.snowflake.client.jdbc.SnowflakeDriver");

// Snowflake-specific properties
config.addDataSourceProperty("warehouse", warehouse);
config.addDataSourceProperty("db", database);
config.addDataSourceProperty("schema", schema);
config.addDataSourceProperty("role", role);

return config;
}

@Bean(name = "snowflakeJdbcTemplate")
public JdbcTemplate snowflakeJdbcTemplate(HikariConfig snowflakeHikariConfig) {
HikariDataSource dataSource = new HikariDataSource(snowflakeHikariConfig);
return new JdbcTemplate(dataSource);
}
}

3.2 MongoDB Configuration

spring:
data:
mongodb:
uri: mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:27017/${MONGODB_DATABASE}?authSource=admin
database: lens

# Connection Pool
auto-index-creation: false

# Repository Configuration
mongodb:
repositories:
enabled: true

Connection Options:

# Full URI with all options
mongodb://user:pass@host1:27017,host2:27017/lens?replicaSet=rs0&maxPoolSize=20&minPoolSize=5&maxIdleTimeMS=300000&connectTimeoutMS=10000&socketTimeoutMS=60000&retryWrites=true&w=majority

Java Bean Configuration:

// MongoConfiguration.java:34
@Configuration
@EnableMongoRepositories(basePackages = "com.ttn.ck.lens.repository.mongo")
public class MongoConfiguration {

@Bean
public MongoClientSettingsBuilderCustomizer mongoClientSettingsBuilderCustomizer() {
return builder -> builder
.applyToConnectionPoolSettings(settings ->
settings.maxSize(20)
.minSize(5)
.maxWaitTime(30, TimeUnit.SECONDS)
.maxConnectionIdleTime(5, TimeUnit.MINUTES))
.applyToSocketSettings(settings ->
settings.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS));
}
}

3.3 MySQL Configuration

spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST}:3306/lens?useSSL=true&requireSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
username: ${MYSQL_USER}
password: ${MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver

# HikariCP Configuration
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
auto-commit: false
connection-test-query: SELECT 1

# JPA Configuration
jpa:
hibernate:
ddl-auto: validate # Production: validate, Dev: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
use_sql_comments: true
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
show-sql: false

3.4 Redis Configuration

spring:
redis:
host: ${REDIS_HOST}
port: 6379
password: ${REDIS_PASSWORD}
database: 0
timeout: 2000ms

# Lettuce Client Configuration
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: -1ms
shutdown-timeout: 100ms

# Cache Configuration
cache:
type: redis
redis:
time-to-live: 900000 # 15 minutes default
cache-null-values: false
key-prefix: "lens:"
use-key-prefix: true

Cache Configuration Bean:

// CacheConfiguration.java:56
@Configuration
@EnableCaching
public class CacheConfiguration {

@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(15))
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
`Map<String, RedisCacheConfiguration>` cacheConfigurations = new Hash`Map<>`();

// Dashboard queries: 15-minute TTL
cacheConfigurations.put("dashboardQueries",
cacheConfiguration().entryTtl(Duration.ofMinutes(15)));

// Filter metadata: 1-hour TTL
cacheConfigurations.put("filterMetadata",
cacheConfiguration().entryTtl(Duration.ofHours(1)));

// RI utilization: 1-hour TTL
cacheConfigurations.put("riUtilization",
cacheConfiguration().entryTtl(Duration.ofHours(1)));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfiguration())
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}

4. External Service Configurations

4.1 RabbitMQ Configuration

spring:
rabbitmq:
host: ${RABBITMQ_HOST}
port: 5672
username: ${RABBITMQ_USER}
password: ${RABBITMQ_PASSWORD}
virtual-host: /lens

# Connection Configuration
connection-timeout: 30000
requested-heartbeat: 60

# Listener Configuration
listener:
simple:
concurrency: 3
max-concurrency: 5
prefetch: 10
acknowledge-mode: auto
retry:
enabled: true
max-attempts: 3
initial-interval: 1000ms
multiplier: 2
max-interval: 10000ms

# Publisher Configuration
publisher-confirms: true
publisher-returns: true
template:
mandatory: true
retry:
enabled: true
max-attempts: 3

Java Bean Configuration:

// RabbitMQConfiguration.java:67
@Configuration
public class RabbitMQConfiguration {

// Exchange declaration
@Bean
public TopicExchange lensEventsExchange() {
return new TopicExchange("lens.events", true, false);
}

// Queue declarations
@Bean
public Queue costUpdateQueue() {
return QueueBuilder.durable("lens.cost.update")
.withArgument("x-message-ttl", 3600000) // 1 hour
.withArgument("x-max-length", 10000)
.build();
}

@Bean
public Queue costCheckQueue() {
return QueueBuilder.durable("lens.cost.check")
.build();
}

@Bean
public Queue reportGenerationQueue() {
return QueueBuilder.durable("lens.reports")
.withArgument("x-message-ttl", 86400000) // 24 hours
.build();
}

// Bindings
@Bean
public Binding costUpdateBinding() {
return BindingBuilder.bind(costUpdateQueue())
.to(lensEventsExchange())
.with("cost.update.*");
}

@Bean
public Binding costCheckBinding() {
return BindingBuilder.bind(costCheckQueue())
.to(lensEventsExchange())
.with("cost.check");
}

// Dead Letter Queue
@Bean
public Queue deadLetterQueue() {
return new Queue("lens.dlq", true);
}
}

4.2 AWS SDK Configuration

aws:
region: us-east-1
credentials:
access-key-id: ${AWS_ACCESS_KEY_ID}
secret-access-key: ${AWS_SECRET_ACCESS_KEY}
# OR use IAM roles (preferred)
use-iam-role: true

cost-explorer:
endpoint: https://ce.us-east-1.amazonaws.com
rate-limit: 5 # requests per second

pricing:
endpoint: https://api.pricing.us-east-1.amazonaws.com

s3:
bucket-name: lens-reports-prod
region: us-east-1

Java Bean Configuration:

// AwsConfiguration.java:89
@Configuration
public class AwsConfiguration {

@Bean
public AWSCredentialsProvider awsCredentialsProvider(
@Value("${aws.credentials.use-iam-role}") boolean useIamRole,
@Value("${aws.credentials.access-key-id:}") String accessKey,
@Value("${aws.credentials.secret-access-key:}") String secretKey) {

if (useIamRole) {
return new DefaultAWSCredentialsProviderChain();
} else {
return new AWSStaticCredentialsProvider(
new BasicAWSCredentials(accessKey, secretKey));
}
}

@Bean
public AmazonCostExplorer costExplorerClient(
AWSCredentialsProvider credentialsProvider,
@Value("${aws.region}") String region) {

return AmazonCostExplorerClientBuilder.standard()
.withRegion(region)
.withCredentials(credentialsProvider)
.withClientConfiguration(
new ClientConfiguration()
.withMaxConnections(50)
.withConnectionTimeout(10000)
.withSocketTimeout(60000)
.withRetryPolicy(PredefinedRetryPolicies.DEFAULT_RETRY_POLICY))
.build();
}

@Bean
public AmazonS3 s3Client(
AWSCredentialsProvider credentialsProvider,
@Value("${aws.s3.region}") String region) {

return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(credentialsProvider)
.build();
}
}

5. Security Configuration

5.1 authX Integration

authx:
enabled: true
jwt:
secret: ${JWT_SECRET}
expiration: 86400000 # 24 hours in milliseconds
header: Authorization
prefix: Bearer

# Security exclusions
excluded-paths:
- /actuator/health
- /actuator/info
- /swagger-ui/**
- /v3/api-docs/**

Java Bean Configuration:

// SecurityConfiguration.java:45
@Configuration
public class SecurityConfiguration {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.anyRequest().authenticated()
);

return http.build();
}
}

5.2 SSL/TLS Configuration

server:
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-alias: lens
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3

6. Logging Configuration

File: src/main/resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"lens","env":"${ENV:-dev}"}</customFields>
</encoder>
</appender>

<!-- File Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/lens.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/lens.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

<!-- Logger Levels -->
<logger name="com.ttn.ck.lens" level="INFO"/>
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="net.snowflake" level="INFO"/>
<logger name="com.mongodb" level="WARN"/>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>

Application-Level Logging Configuration:

logging:
level:
root: INFO
com.ttn.ck.lens: INFO
org.springframework.web: WARN
org.hibernate: WARN
net.snowflake.client.jdbc: INFO

pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

file:
name: logs/lens.log
max-size: 100MB
max-history: 30

7. Performance Tuning Configuration

7.1 Async Configuration

async:
core-pool-size: 10
max-pool-size: 20
queue-capacity: 100
thread-name-prefix: "lens-async-"
keep-alive-seconds: 60

Java Bean Configuration:

// AsyncConfiguration.java:34
@Configuration
@EnableAsync
public class AsyncConfiguration {

@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor(
@Value("${async.core-pool-size}") int corePoolSize,
@Value("${async.max-pool-size}") int maxPoolSize,
@Value("${async.queue-capacity}") int queueCapacity) {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("lens-async-");
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}

7.2 Retry Configuration

retry:
max-attempts: 3
initial-delay: 1000
multiplier: 2
max-delay: 10000

Java Bean Configuration:

// RetryConfiguration.java:45
@Configuration
@EnableRetry
public class RetryConfiguration {

@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();

FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);

SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);

return retryTemplate;
}
}

7.3 Circuit Breaker Configuration

resilience4j:
circuitbreaker:
instances:
snowflake:
register-health-indicator: true
sliding-window-size: 10
minimum-number-of-calls: 5
permitted-number-of-calls-in-half-open-state: 3
automatic-transition-from-open-to-half-open-enabled: true
wait-duration-in-open-state: 60s
failure-rate-threshold: 50
event-consumer-buffer-size: 10

aws:
sliding-window-size: 10
failure-rate-threshold: 60
wait-duration-in-open-state: 30s

8. Actuator & Monitoring Configuration

management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,caches,env
base-path: /actuator

endpoint:
health:
show-details: when-authorized
probes:
enabled: true
metrics:
enabled: true

metrics:
export:
prometheus:
enabled: true
step: 1m
tags:
application: ${spring.application.name}
environment: ${spring.profiles.active}

health:
redis:
enabled: true
mongo:
enabled: true
db:
enabled: true

Custom Health Indicators:

// SnowflakeHealthIndicator.java:23
@Component
public class SnowflakeHealthIndicator implements HealthIndicator {

@Autowired
private JdbcTemplate snowflakeJdbcTemplate;

@Override
public Health health() {
try {
snowflakeJdbcTemplate.queryForObject("SELECT 1", Integer.class);
return Health.up()
.withDetail("database", "Snowflake")
.withDetail("status", "Available")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("database", "Snowflake")
.withDetail("error", e.getMessage())
.build();
}
}
}

9. Environment-Specific Configurations

9.1 Development Environment

File: application-dev.yml

spring:
profiles:
active: dev

snowflake:
url: jdbc:snowflake://dev-account.snowflakecomputing.com
warehouse: LENS_DEV_WH
database: COST_DEV_DB

spring:
datasource:
url: jdbc:mysql://localhost:3306/lens_dev

redis:
host: localhost

rabbitmq:
host: localhost

logging:
level:
com.ttn.ck.lens: DEBUG

# Enable SQL logging in dev
jpa:
show-sql: true
properties:
hibernate:
format_sql: true

9.2 UAT Environment

File: application-uat.yml

spring:
profiles:
active: uat

snowflake:
url: jdbc:snowflake://uat-account.snowflakecomputing.com
warehouse: LENS_UAT_WH
database: COST_UAT_DB

logging:
level:
com.ttn.ck.lens: INFO

9.3 Production Environment

File: application-prod.yml

spring:
profiles:
active: prod

snowflake:
url: jdbc:snowflake://prod-account.snowflakecomputing.com
warehouse: LENS_PROD_WH
database: COST_PROD_DB
hikari:
maximum-pool-size: 30 # Higher in production

server:
tomcat:
max-threads: 300 # Higher in production

logging:
level:
com.ttn.ck.lens: INFO
org.springframework: WARN

# Disable SQL logging in production
spring:
jpa:
show-sql: false

10. Feature Flags Configuration

features:
cost-alerts:
enabled: true
ri-recommendations:
enabled: true
cudos-dashboard:
enabled: true
report-generation:
enabled: true
max-concurrent-reports: 5
cache-warming:
enabled: true
cron: "0 0 6 * * ?" # 6 AM daily

Usage in Code:

// FeatureService.java:23
@Service
public class FeatureService {

@Value("${features.cost-alerts.enabled}")
private boolean costAlertsEnabled;

public boolean isCostAlertsEnabled() {
return costAlertsEnabled;
}
}

11. Configuration Best Practices

11.1 Externalize Secrets

Don't:

# Bad - secrets in config file
snowflake:
password: MySecretPassword123

Do:

# Good - use environment variables
snowflake:
password: ${SNOWFLAKE_PASSWORD}

Better - Use Config Server:

# Fetch from Config Server which reads from Vault/AWS Secrets Manager
spring:
cloud:
config:
uri: http://config-server

11.2 Profile-Specific Overrides

# application.yml (defaults)
cache:
ttl: 900 # 15 minutes

---
# application-prod.yml (production override)
cache:
ttl: 1800 # 30 minutes in production

11.3 Configuration Validation

// ConfigurationValidator.java:34
@Component
public class ConfigurationValidator implements InitializingBean {

@Value("${snowflake.url}")
private String snowflakeUrl;

@Override
public void afterPropertiesSet() throws Exception {
if (snowflakeUrl == null || snowflakeUrl.isEmpty()) {
throw new IllegalStateException("Snowflake URL must be configured");
}
}
}

12. Runtime Configuration Refresh

Refresh Config Without Restart

# Trigger config refresh
curl -X POST http://localhost:8080/actuator/refresh

Enable Refresh Scope:

@Component
@RefreshScope
public class DynamicConfigComponent {

@Value("${dynamic.property}")
private String dynamicProperty;

// This property will be refreshed when /actuator/refresh is called
}

Summary

Configuration Files:

  • bootstrap.yml (Config Server connection)
  • application.yml (main config)
  • application-{profile}.yml (environment overrides)
  • logback.xml (logging)

Key Configuration Areas:

  • 4 Databases (Snowflake, MongoDB, MySQL, Redis)
  • 2 Message systems (RabbitMQ)
  • 1 External API (AWS SDK)
  • Security (JWT, SSL/TLS)
  • Performance (thread pools, caching, retry, circuit breaker)
  • Monitoring (Actuator, Prometheus)

Best Practices:

  • Externalize secrets using environment variables
  • Use Config Server for centralized configuration
  • Profile-specific overrides for environment differences
  • Enable configuration refresh without restart
  • Validate critical configurations at startup

Document Version: 1.0 Last Updated: October 25, 2025