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