Configuration Best Practices
Giriş
Bir uygulamanın konfigürasyonu, onun DNA'sıdır. Veritabanı adresi, API key'leri, cache süreleri, feature flag'leri — bunlar uygulamanın nasıl davranacağını belirler. Yanlış bir konfigürasyon, uygulamanızı production'da çökertebilir. Doğru bir konfigürasyon stratejisi ise uygulamanızı farklı ortamlarda güvenle çalıştırmanızı, secret'ları korumanızı ve değişiklikleri restart'sız uygulamanızı sağlar.
Bunu bir arabanın kontrol paneli gibi düşünün: hız, devir, yakıt, motor sıcaklığı — her şey doğru göstergelerde, doğru zamanda, doğru değerlerle olmalı. Yanlış kablolama yaparsanız hız göstergesi sıcaklığı, yakıt göstergesi devri gösterir — kaos.
Bu derste Spring Boot'un konfigürasyon sistemini derinlemesine öğrenecek, type-safe configuration, profil yönetimi, secret management ve production-ready best practice'leri uygulayacağız.
Konfigürasyon Hiyerarşisi (Öncelik Sırası)
Spring Boot, konfigürasyon değerlerini birden fazla kaynaktan okur. Bu kaynaklar belirli bir öncelik sırasına sahiptir — üstteki alttakini ezer (override eder):
1. Command line arguments (--server.port=9090)
2. SPRING_APPLICATION_JSON (inline JSON)
3. ServletConfig / ServletContext (web parametreleri)
4. JNDI attributes (java:comp/env)
5. Java System properties (-Dserver.port=9090)
6. OS environment variables (SERVER_PORT=9090)
7. RandomValuePropertySource (random.int, random.uuid)
8. application-{profile}.yml (profil-specific)
9. application.yml (varsayılan)
10. @PropertySource (özel dosyalar)
11. SpringApplication defaults (kod içi varsayılan)Bu hiyerarşi neden önemli? Çünkü aynı property'yi farklı yerlerde tanımlayabilirsiniz ve Spring Boot hangisinin kazanacağını bu sıraya göre belirler:
# application.yml — en düşük öncelik
server:
port: 8080# Environment variable — daha yüksek öncelik
export SERVER_PORT=9090# Command line — EN yüksek öncelik
java -jar app.jar --server.port=9999Bu durumda uygulama 9999 portunda çalışır. Bu mekanizma, aynı JAR dosyasını farklı ortamlarda farklı konfigürasyonlarla çalıştırmanızı sağlar — Twelve-Factor App'in "Config" prensibinin temelidir.
application.yml vs application.properties
Spring Boot iki format destekler. Her ikisi de aynı işi yapar, ancak YAML daha okunabilir ve hiyerarşik yapıyı daha iyi ifade eder:
# application.properties — düz format
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=secret
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5# application.yml — hiyerarşik format
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: secret
hikari:
maximum-pool-size: 20
minimum-idle: 5💡 İpucu: Projelerde YAML tercih edin. Nested yapıları daha temiz ifade eder, liste desteği doğaldır ve daha az tekrar gerektirir. Ancak YAML'ın girintilere (indentation) duyarlı olduğunu unutmayın — bir boşluk hatası tüm konfigürasyonu bozabilir.
Profile (Ortam) Yönetimi
Gerçek dünya uygulamaları farklı ortamlarda çalışır: development, testing, staging, production. Her ortamın farklı konfigürasyona ihtiyacı vardır. Spring Boot'un profil sistemi bunu zarif bir şekilde çözer.
Profil Dosyaları
src/main/resources/
├── application.yml # Tüm ortamlar için ortak
├── application-dev.yml # Development
├── application-test.yml # Test
├── application-staging.yml # Staging
└── application-prod.yml # Production# application.yml — ortak konfigürasyon
spring:
application:
name: order-service
jpa:
open-in-view: false
server:
servlet:
context-path: /api# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:devdb
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
h2:
console:
enabled: true
logging:
level:
com.example: DEBUG
org.hibernate.SQL: DEBUG# application-prod.yml
spring:
datasource:
url: ${DATABASE_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 30
minimum-idle: 10
connection-timeout: 20000
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
com.example: INFO
org.hibernate: WARN
server:
port: ${PORT:8080}Profil Aktivasyonu
# Environment variable ile
export SPRING_PROFILES_ACTIVE=prod
# Command line ile
java -jar app.jar --spring.profiles.active=prod
# Docker'da
docker run -e SPRING_PROFILES_ACTIVE=prod myapp:latest
# application.yml'da varsayılan profil
spring:
profiles:
active: devProfil Grupları (Spring Boot 2.4+)
Birden fazla profili gruplamak için:
# application.yml
spring:
profiles:
group:
prod:
- prod-db
- prod-cache
- prod-security
dev:
- dev-db
- dev-cacheBu sayede SPRING_PROFILES_ACTIVE=prod dediğinizde prod-db, prod-cache ve prod-security profilleri otomatik aktif olur.
⚠️ Dikkat: Production'da
ddl-auto: create-dropveyaupdateASLA kullanmayın. Bu, tabloları silip yeniden oluşturur veya yapısını değiştirir. Production'davalidatekullanın — sadece entity ile tablo yapısının eşleştiğini doğrular. Schema değişiklikleri için Flyway veya Liquibase kullanın.
@Value — Basit Değer Enjeksiyonu
Tek bir property değerini bean'e enjekte etmenin en basit yolu:
@Service
public class NotificationService {
@Value("${app.notification.email.from}")
private String fromEmail;
@Value("${app.notification.email.enabled:true}") // varsayılan değer
private boolean emailEnabled;
@Value("${app.notification.max-retry:3}")
private int maxRetry;
@Value("${app.notification.recipients}") // virgülle ayrılmış liste
private List<String> recipients;
@Value("#{${app.notification.templates}}") // SpEL ile Map
private Map<String, String> templates;
}app:
notification:
email:
from: noreply@example.com
enabled: true
max-retry: 3
recipients: admin@example.com,ops@example.com
templates: "{welcome: 'welcome.html', reset: 'reset-password.html'}"@Value'nun Sınırları
@Value basit senaryolar için uygundur, ancak ciddi dezavantajları vardır:
// ❌ KÖTÜ — @Value ile 10+ property yönetmek
@Service
public class PaymentService {
@Value("${payment.gateway.url}")
private String gatewayUrl;
@Value("${payment.gateway.api-key}")
private String apiKey;
@Value("${payment.gateway.secret}")
private String secret;
@Value("${payment.gateway.timeout:5000}")
private int timeout;
@Value("${payment.gateway.retry:3}")
private int retry;
@Value("${payment.gateway.sandbox:false}")
private boolean sandbox;
// 6 tane @Value — karışık, hata yapma riski yüksek
// Typo yaparsan runtime'da patlıyor!
}Sorunlar:
Property adında typo yaparsanız derleme zamanında hata almaz, runtime'da patlarsınız
Tip dönüşüm hatalarını önceden yakalayamazsınız
Validasyon yapılamaz
Refactoring zor — property adını değiştirince tüm
@Valuekullanımlarını bulmanız gerekirTest etmesi zor
@ConfigurationProperties — Type-Safe Configuration
@ConfigurationProperties, Spring Boot'un en güçlü konfigürasyon mekanizmasıdır. Property'leri tip-güvenli (type-safe) bir Java nesnesine bağlar:
@ConfigurationProperties(prefix = "app.payment")
@Validated
public record PaymentConfig(
@NotBlank String gatewayUrl,
@NotBlank String apiKey,
@NotBlank String secret,
@Min(1000) @Max(30000) int timeout,
@Min(0) @Max(10) int retry,
boolean sandbox,
@Valid RateLimitConfig rateLimit
) {
public record RateLimitConfig(
@Min(1) int requestsPerSecond,
@Min(1) int burstCapacity
) {}
}app:
payment:
gateway-url: https://api.payment.com/v2
api-key: ${PAYMENT_API_KEY}
secret: ${PAYMENT_SECRET}
timeout: 5000
retry: 3
sandbox: false
rate-limit:
requests-per-second: 100
burst-capacity: 200// Kullanım — temiz, type-safe, IDE auto-complete çalışır
@Service
@RequiredArgsConstructor
public class PaymentService {
private final PaymentConfig config;
public PaymentResult charge(PaymentRequest request) {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(config.timeout()))
.build();
if (config.sandbox()) {
log.info("SANDBOX MODE — gerçek ödeme yapılmıyor");
return PaymentResult.sandboxSuccess();
}
// config.gatewayUrl(), config.apiKey() — typo imkansız
return executePayment(client, request);
}
}Aktivasyon
@SpringBootApplication
@ConfigurationPropertiesScan // Paket altındaki tüm @ConfigurationProperties'i tarar
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
// Veya tek tek kayıt:
@Configuration
@EnableConfigurationProperties(PaymentConfig.class)
public class AppConfig {}Class vs Record
Java 17+ kullanıyorsanız record kullanın — daha az boilerplate, otomatik immutable:
// ✅ Record ile — temiz, immutable, otomatik getter/toString/equals
@ConfigurationProperties(prefix = "app.mail")
@Validated
public record MailConfig(
@NotBlank String host,
@Min(1) @Max(65535) int port,
@NotBlank String username,
@NotBlank String password,
boolean starttls,
@Valid PoolConfig pool
) {
public record PoolConfig(
@Min(1) int coreSize,
@Min(1) int maxSize,
@Min(1000) int keepAlive
) {}
}// Geleneksel class ile — daha fazla boilerplate ama setter ile mutable
@ConfigurationProperties(prefix = "app.mail")
@Validated
@Getter
@Setter
public class MailConfig {
@NotBlank
private String host;
@Min(1) @Max(65535)
private int port;
@NotBlank
private String username;
@NotBlank
private String password;
private boolean starttls = false;
@Valid
private PoolConfig pool = new PoolConfig();
@Getter
@Setter
public static class PoolConfig {
@Min(1) private int coreSize = 5;
@Min(1) private int maxSize = 10;
@Min(1000) private int keepAlive = 60000;
}
}💡 İpucu: Record kullandığınızda varsayılan değer constructor'dan gelir. Property dosyasında tanımlı olmayan bir değer için record constructor'a default parametre ekleyemezsiniz — bu yüzden YAML'da tüm değerleri belirtmeniz gerekir veya
@DefaultValueannotation kullanın:
@ConfigurationProperties(prefix = "app.cache")
public record CacheConfig(
@DefaultValue("true") boolean enabled,
@DefaultValue("3600") int ttlSeconds,
@DefaultValue("1000") int maxSize
) {}Konfigürasyon Validasyonu
Spring Boot, @Validated annotation'ı ile konfigürasyon değerlerini uygulama başlangıcında doğrular. Yanlış konfigürasyon varsa uygulama başlamaz — fail-fast prensibi:
@ConfigurationProperties(prefix = "app.database")
@Validated
public record DatabaseConfig(
@NotBlank(message = "Database URL boş olamaz")
String url,
@NotBlank
String username,
@NotBlank
String password,
@Min(value = 1, message = "Pool size en az 1 olmalı")
@Max(value = 100, message = "Pool size en fazla 100 olabilir")
int poolSize,
@DurationMin(seconds = 1)
@DurationMax(seconds = 60)
Duration connectionTimeout,
@Pattern(regexp = "^(postgresql|mysql|h2)$",
message = "Desteklenen DB türleri: postgresql, mysql, h2")
String type
) {}Eğer geçersiz bir değer verilirse uygulama hemen başlamaz:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target app.database failed:
Property: app.database.url
Value: (empty)
Reason: Database URL boş olamaz
Property: app.database.pool-size
Value: -5
Reason: Pool size en az 1 olmalıBu davranış kritiktir — yanlış konfigürasyonla çalışan bir uygulama, başlamayan bir uygulamadan çok daha tehlikelidir. Production'da yanlış veritabanına bağlanmak, bağlanamamaktan kötüdür.
Secret Management — Gizli Bilgi Yönetimi
Konfigürasyon yönetiminin en kritik konularından biri secret'ların güvenli yönetimidir. Veritabanı şifreleri, API key'leri, JWT secret'ları — bunlar asla kaynak kodunda veya property dosyalarında açık metin olarak bulunmamalıdır.
Seviye 1: Hardcoded (❌ ASLA)
# ❌ EN KÖTÜ — Git history'de sonsuza dek kalır
spring:
datasource:
password: SuperSecret123!
app:
jwt:
secret: myJwtSecretKey12345
payment:
api-key: sk_live_abc123def456Bu yaklaşımın tehlikeleri:
Git repository'ye push edilir, tüm ekip görür
Git history'den silmek neredeyse imkansız
Repo public olursa tüm secret'lar ifşa olur
Ortam ayrımı yapılamaz (dev ve prod aynı secret'ı kullanır)
Seviye 2: Environment Variables (✅ Minimum)
# application-prod.yml
spring:
datasource:
url: ${DATABASE_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
app:
jwt:
secret: ${JWT_SECRET}
payment:
api-key: ${PAYMENT_API_KEY}# Docker / Kubernetes'de
export DATABASE_URL=jdbc:postgresql://prod-db:5432/mydb
export DB_USERNAME=app_user
export DB_PASSWORD=SuperSecret123!
export JWT_SECRET=myLongRandomSecretKeyForJWT# docker-compose.yml
services:
app:
environment:
DATABASE_URL: jdbc:postgresql://db:5432/mydb
DB_PASSWORD: ${DB_PASSWORD} # .env dosyasından⚠️ Dikkat: Environment variable'lar process listesinde (
ps aux), Docker inspect'te ve crash dump'larda görünebilir. Minimum seviye olarak kabul edilir, ancak hassas ortamlar için yeterli değildir.
Seviye 3: External Secret Store (✅✅ Önerilen)
Production ortamlar için HashiCorp Vault, AWS Secrets Manager veya Azure Key Vault gibi çözümler kullanın:
<!-- HashiCorp Vault entegrasyonu -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency># application.yml
spring:
cloud:
vault:
uri: https://vault.example.com
token: ${VAULT_TOKEN}
kv:
backend: secret
default-context: order-service
authentication: TOKEN
config:
import: optional:vault://// Vault'taki secret'lar otomatik olarak property olarak erişilebilir
@ConfigurationProperties(prefix = "app.database")
public record DatabaseConfig(
String url, // vault: secret/order-service → app.database.url
String username, // vault: secret/order-service → app.database.username
String password // vault: secret/order-service → app.database.password
) {}Seviye 4: Kubernetes Secrets + Volume Mount
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
db-password: U3VwZXJTZWNyZXQxMjMh # base64 encoded
jwt-secret: bXlKd3RTZWNyZXRLZXk=# Spring Boot — dosyadan secret okuma
spring:
config:
import: optional:file:/etc/secrets/
datasource:
password: ${db-password}Relaxed Binding — Esnek Bağlama
Spring Boot, property isimlerinde esnek eşleştirme yapar. Aşağıdaki tüm formatlar aynı property'yi ifade eder:
# Aşağıdakiler HEPSİ aynı property'ye bağlanır:
app:
payment-gateway: # kebab-case (YAML önerilen)
api-key: abc
# application.properties'de:
app.paymentGateway.apiKey # camelCase
app.payment-gateway.api-key # kebab-case
app.payment_gateway.api_key # underscore
APP_PAYMENTGATEWAY_APIKEY # UPPER_CASE (env variable)@ConfigurationProperties(prefix = "app.payment-gateway")
public record PaymentGatewayConfig(
String apiKey // Java'da camelCase — tüm formatlara bağlanır
) {}💡 İpucu: YAML dosyalarında kebab-case (
api-key), Java kodunda camelCase (apiKey), environment variable'larda UPPER_CASE (API_KEY) kullanın. Spring Boot hepsini otomatik eşleştirir.
Custom Configuration Metadata
IDE auto-complete ve dokümantasyon için konfigürasyon metadata dosyası oluşturun:
// src/main/resources/META-INF/additional-spring-configuration-metadata.json
{
"properties": [
{
"name": "app.payment.gateway-url",
"type": "java.lang.String",
"description": "Payment gateway base URL",
"defaultValue": "https://api.payment.com/v2"
},
{
"name": "app.payment.timeout",
"type": "java.lang.Integer",
"description": "Payment request timeout in milliseconds",
"defaultValue": 5000
},
{
"name": "app.payment.sandbox",
"type": "java.lang.Boolean",
"description": "Enable sandbox mode for testing",
"defaultValue": false
}
]
}Spring Boot annotation processor ile otomatik metadata üretimi:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>Bu dependency eklendiğinde, @ConfigurationProperties sınıflarınız için metadata otomatik üretilir ve IDE'de auto-complete çalışır.
Conditional Configuration — Koşullu Konfigürasyon
Belirli koşullara göre farklı bean'ler veya konfigürasyonlar aktif edebilirsiniz:
@Configuration
public class CacheConfig {
// Redis varsa Redis cache kullan
@Bean
@ConditionalOnProperty(name = "app.cache.type", havingValue = "redis")
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)))
.build();
}
// Redis yoksa in-memory cache kullan
@Bean
@ConditionalOnProperty(name = "app.cache.type",
havingValue = "memory",
matchIfMissing = true)
public CacheManager inMemoryCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10)));
return manager;
}
}# Dev ortamında
app:
cache:
type: memory
# Prod ortamında
app:
cache:
type: redisProperty Placeholder ve SpEL
Konfigürasyon değerlerinde dinamik ifadeler kullanabilirsiniz:
app:
name: order-service
version: 1.0.0
description: "${app.name} v${app.version}" # Property placeholder
server:
port: ${PORT:8080} # Varsayılan değer
host: ${HOSTNAME:localhost}
base-url: "http://${app.server.host}:${app.server.port}"
feature:
new-ui: ${FEATURE_NEW_UI:false}@Service
public class AppInfoService {
// SpEL (Spring Expression Language) ile
@Value("#{${app.limits} ?: 100}")
private int limit;
@Value("#{systemProperties['user.timezone']}")
private String timezone;
@Value("#{T(java.lang.Runtime).getRuntime().availableProcessors()}")
private int cpuCores;
}Yaygın Hatalar ve Çözümleri
1. Secret'ları Commit Etmek
# ❌ YANLIŞ
spring:
datasource:
password: prod_password_123
# ✅ DOĞRU
spring:
datasource:
password: ${DB_PASSWORD}Çözüm: .gitignore'a .env dosyasını ekleyin. Git hook ile secret pattern'leri kontrol edin:
# .gitignore
.env
*.env
application-local.yml2. Profil Karışıklığı
# ❌ YANLIŞ — prod profile dosyasında dev ayarları
# application-prod.yml
spring:
jpa:
hibernate:
ddl-auto: update # ← PROD'da asla!
show-sql: true # ← Performans kaybı
# ✅ DOĞRU
# application-prod.yml
spring:
jpa:
hibernate:
ddl-auto: validate
show-sql: false3. @Value ile Typo
// ❌ YANLIŞ — typo fark edilmez, runtime'da patlıyor
@Value("${app.paymnet.url}") // "payment" değil "paymnet"
private String paymentUrl;
// ✅ DOĞRU — @ConfigurationProperties ile derleme zamanında hata
@ConfigurationProperties(prefix = "app.payment")
public record PaymentConfig(String url) {}4. Validasyon Eksikliği
// ❌ YANLIŞ — pool size -1 olsa bile uygulama başlar, çöker
@Value("${app.db.pool-size}")
private int poolSize;
// ✅ DOĞRU — geçersiz değerde uygulama başlamaz
@ConfigurationProperties(prefix = "app.db")
@Validated
public record DbConfig(
@Min(1) @Max(100) int poolSize
) {}5. Tüm Ortamlarda Aynı Konfigürasyon
# ❌ YANLIŞ — dev, staging, prod hepsi aynı
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
# ✅ DOĞRU — ortama göre farklı
# application-dev.yml → H2 in-memory
# application-staging.yml → staging DB
# application-prod.yml → ${DATABASE_URL}Gerçek Dünya Örneği: E-Ticaret Konfigürasyonu
Bir e-ticaret uygulamasının tam konfigürasyon yapısı:
@ConfigurationProperties(prefix = "app")
@Validated
public record AppConfig(
@Valid PaymentConfig payment,
@Valid ShippingConfig shipping,
@Valid NotificationConfig notification,
@Valid CacheConfig cache,
@Valid SecurityConfig security
) {
public record PaymentConfig(
@NotBlank String gatewayUrl,
@NotBlank String apiKey,
@Min(1000) int timeoutMs,
@Min(0) @Max(5) int maxRetry,
boolean sandboxMode,
@Valid List<@NotBlank String> supportedMethods
) {}
public record ShippingConfig(
@NotBlank String providerUrl,
@Positive BigDecimal freeShippingThreshold,
@Min(1) @Max(30) int estimatedDaysDefault,
Map<String, BigDecimal> zonePrices
) {}
public record NotificationConfig(
@Valid EmailConfig email,
@Valid SmsConfig sms,
boolean enabled
) {
public record EmailConfig(
@NotBlank @Email String from,
@NotBlank String host,
@Min(1) @Max(65535) int port,
boolean starttls
) {}
public record SmsConfig(
@NotBlank String provider,
@NotBlank String apiKey,
@NotBlank String fromNumber
) {}
}
public record CacheConfig(
boolean enabled,
@Min(60) int ttlSeconds,
@Min(100) int maxEntries,
String type
) {}
public record SecurityConfig(
@NotBlank String jwtSecret,
@Min(300) int jwtExpirationSeconds,
@Min(1) int maxLoginAttempts,
@Min(60) int lockoutDurationSeconds
) {}
}# application.yml — ortak
app:
cache:
enabled: true
ttl-seconds: 3600
max-entries: 5000
type: memory
notification:
enabled: true
security:
max-login-attempts: 5
lockout-duration-seconds: 900
# application-prod.yml
app:
payment:
gateway-url: https://api.stripe.com/v1
api-key: ${STRIPE_API_KEY}
timeout-ms: 10000
max-retry: 3
sandbox-mode: false
supported-methods:
- CREDIT_CARD
- DEBIT_CARD
- BANK_TRANSFER
shipping:
provider-url: https://api.shipping.com
free-shipping-threshold: 150.00
estimated-days-default: 3
zone-prices:
local: 9.99
national: 19.99
international: 49.99
notification:
email:
from: orders@myshop.com
host: ${SMTP_HOST}
port: 587
starttls: true
sms:
provider: twilio
api-key: ${TWILIO_API_KEY}
from-number: ${TWILIO_FROM_NUMBER}
cache:
type: redis
ttl-seconds: 1800
security:
jwt-secret: ${JWT_SECRET}
jwt-expiration-seconds: 3600// Kullanım — her yerde temiz, type-safe erişim
@Service
@RequiredArgsConstructor
public class OrderService {
private final AppConfig config;
public ShippingCost calculateShipping(BigDecimal orderTotal, String zone) {
if (orderTotal.compareTo(
config.shipping().freeShippingThreshold()) >= 0) {
return ShippingCost.free();
}
BigDecimal price = config.shipping().zonePrices()
.getOrDefault(zone, BigDecimal.valueOf(19.99));
return new ShippingCost(price,
config.shipping().estimatedDaysDefault());
}
public boolean isPaymentMethodSupported(String method) {
return config.payment().supportedMethods().contains(method);
}
}Best Practices Kontrol Listesi
@ConfigurationProperties kullanın —
@Valueyerine type-safe binding tercih edinValidasyon ekleyin —
@Validatedile geçersiz konfigürasyonda fail-fastSecret'ları dışarıda tutun — En az environment variable, ideal olarak Vault
Profil kullanın — Her ortam için ayrı konfigürasyon dosyası
Varsayılan değerler belirleyin —
${PROP:default}formatı ileYAML tercih edin — Hiyerarşik yapıyı daha iyi ifade eder
Metadata oluşturun —
spring-boot-configuration-processorile IDE desteğiKonfigürasyonu test edin —
@SpringBootTestile konfigürasyon validasyonunu test edinProduction'da ddl-auto: validate — Asla
updateveyacreate-drop.gitignore —
.env,application-local.ymldosyalarını commit etmeyin
Konfigürasyon Test Etme
@SpringBootTest
@ActiveProfiles("test")
class AppConfigTest {
@Autowired
private AppConfig config;
@Test
void paymentConfigShouldBeValid() {
assertThat(config.payment().gatewayUrl()).isNotBlank();
assertThat(config.payment().timeoutMs()).isGreaterThan(0);
assertThat(config.payment().maxRetry()).isBetween(0, 5);
}
@Test
void securityConfigShouldBeValid() {
assertThat(config.security().jwtSecret()).isNotBlank();
assertThat(config.security().jwtExpirationSeconds())
.isGreaterThanOrEqualTo(300);
}
}Özet
Konfigürasyon hiyerarşisi: Command line > env variable > application-{profile}.yml > application.yml — üstteki alttakini ezer
@ConfigurationProperties > @Value: Type-safe, validasyon destekli, IDE-friendly, refactoring-uyumlu
Secret management: Hardcoded ❌ → Environment variable ✅ → Vault/Secret Store ✅✅
Profile sistemi: Her ortam (dev/staging/prod) için ayrı konfigürasyon dosyası kullanın
Fail-fast:
@Validatedile yanlış konfigürasyonda uygulama başlamasın — runtime sürprizi olmasınMetadata:
spring-boot-configuration-processorile IDE auto-complete ve dokümantasyon
AI Asistan
Sorularını yanıtlamaya hazır