Spring Boot/Profiles — dev/staging/prod, profile-specific bean, profile groups
~22 phútSpring Boot FoundationsMiễn phí

Profiles — dev/staging/prod, profile-specific bean, profile groups

Profile là cơ chế Boot toggle config + bean theo môi trường. Bài này bóc cách profile activate, @Profile trên @Bean và @Component, profile groups, default profile, multi-document YAML, profile expression (! && ||), profile inheritance, và pattern multi-tenant với profile.

Bài 04 đã giới thiệu application-\{profile\}.yml. Bài này đào sâu profile như cơ chế cross-cutting: không chỉ override property file, mà còn toggle bean theo môi trường. Pattern Spring profile là cách clean nhất để có 1 codebase chạy đúng trên 5 môi trường (local/dev/staging/prod/test) mà không phải if (env == "prod") rải rác trong code.

1. Profile là gì — về mặt khái niệm

Profile là named tag gắn vào bean hoặc config. Khi 1 profile được "active", Boot:

  • Load file application-\{profile\}.yml (đã thấy bài 04).
  • Register bean có @Profile("\{profile\}") matching, skip bean không match.
  • Activate @PropertySource profile-specific.

Multiple profile có thể active cùng lúc. App có thể không có profile nào active (= "default" profile).

flowchart LR
    Start[App start]
    Active["spring.profiles.active=prod,monitoring"]
    Filter1["application-prod.yml<br/>application-monitoring.yml<br/>load + override"]
    Filter2["@Profile('prod') beans → register<br/>@Profile('monitoring') beans → register<br/>@Profile('dev') beans → SKIP"]
    Final[Container with prod + monitoring config]

    Start --> Active --> Filter1 --> Filter2 --> Final

    style Active fill:#fef3c7

2. Activate profile — 5 cách

2.1 Property spring.profiles.active

# application.yml — set default profile
spring:
  profiles:
    active: dev
# Override khi run
java -jar app.jar --spring.profiles.active=prod
# Hoac qua env var (relax binding)
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

2.2 JVM system property

java -Dspring.profiles.active=prod -jar app.jar

2.3 Programmatic

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(App.class);
    app.setAdditionalProfiles("prod");          // them, khong replace
    app.run(args);
}

Hoặc:

ConfigurableEnvironment env = ctx.getEnvironment();
env.setActiveProfiles("prod");                  // replace
env.addActiveProfile("monitoring");             // them

2.4 Test annotation

@SpringBootTest
@ActiveProfiles("test")
class OrderServiceTest { ... }

2.5 Multiple profile cùng lúc

spring:
  profiles:
    active: prod,monitoring,asia-east     # 3 profile cung active

Boot apply theo thứ tự — profile sau override profile trước. Pattern phổ biến: 1 profile cho env (prod), 1 cho region (asia-east), 1 cho feature (monitoring).

3. @Profile trên bean

3.1 @Profile cơ bản

@Configuration
public class CacheConfig {

    @Bean
    @Profile("dev")
    public CacheManager devCache() {
        return new ConcurrentMapCacheManager();      // in-memory cho dev
    }

    @Bean
    @Profile("prod")
    public CacheManager prodCache(RedisConnectionFactory factory) {
        return new RedisCacheManager(factory);        // Redis cho prod
    }
}

Active profile dev → register devCache, skip prodCache. Active prod → ngược lại. 1 bean type (CacheManager), 2 implementation tuỳ profile — không cần if-else trong code business.

3.2 @Profile trên @Component

@Component
@Profile("prod")
public class CloudWatchMetricsExporter implements MetricsExporter { ... }

@Component
@Profile("dev")
public class ConsoleMetricsExporter implements MetricsExporter { ... }

Service inject MetricsExporter — Boot pick implementation theo profile. Strategy pattern + profile = clean.

3.3 @Profile trên @Configuration class

@Configuration
@Profile("prod")
public class ProductionConfig {

    @Bean public DataSource dataSource() { ... }
    @Bean public CacheManager cache() { ... }
    @Bean public MetricsExporter metrics() { ... }
}

Cả class @Configuration chỉ register khi profile active. Pattern này gom config theo profile — clean cho team biết "muốn xem prod config, mở ProductionConfig".

3.4 Profile expression — ! && ||

Boot 2.4+ support expression:

@Profile("!dev")                                // moi profile TRU dev
@Profile("dev | test")                          // dev HOAC test
@Profile("prod & monitoring")                   // prod VA monitoring (Boot 5.1+ syntax)
@Profile({"dev", "test"})                       // OR (legacy syntax)

Operator:

  • ! — NOT
  • | (Boot 2.4+) hoặc array — OR
  • & (Boot 5.1+, Spring 6) — AND

Use case:

@Component
@Profile("!prod")     // dev, test, staging tat ca run, tru prod
public class DebugInterceptor { ... }

@Component
@Profile("prod & asia-east")
public class ChinaSpecificFilter { ... }

4. Profile groups — gom logic

Boot 2.4+ support profile groups — gom nhiều profile thành 1 logic:

spring:
  profiles:
    group:
      production: prod, prod-db, prod-cache, prod-tracing
      staging: staging, staging-db, staging-cache

Activate production:

java -jar app.jar --spring.profiles.active=production

Boot tự include 4 profile: prod, prod-db, prod-cache, prod-tracing. Tách thành 4 file YAML → mỗi concern 1 file:

application-prod.yml              # Spring config common cho prod
application-prod-db.yml           # DataSource config
application-prod-cache.yml        # Redis config
application-prod-tracing.yml      # Observability config

Lợi ích: file nhỏ, single concern, easy review. Khi application-prod.yml lên 200 dòng, group là cách clean.

5. Default profile

Bean không có @Profile annotation → register ở mọi profile. Bean với @Profile("default") chỉ register khi không profile nào active:

@Component
public class CommonService { ... }              // moi profile

@Component
@Profile("default")                              // CHI khi khong profile active
public class FallbackService { ... }

@Component
@Profile({"!dev", "!test"})                      // moi profile tru dev va test
public class ProductionLogger { ... }

spring.profiles.default — profile dùng khi spring.profiles.active empty:

spring:
  profiles:
    default: dev          # neu khong active gi, dung 'dev'

Pattern: development team chạy local không set SPRING_PROFILES_ACTIVE → fall back dev profile. CI/CD set explicit profile.

6. Multi-document YAML — nhiều profile trong 1 file

Bài 04 đã giới thiệu — đây là chi tiết:

# application.yml — single file, multi document
spring:
  application:
    name: order-service
  datasource:
    hikari:
      maximum-pool-size: 10           # default

---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:postgresql://localhost:5432/dev
  logging:
    level:
      com.olhub: DEBUG

---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:postgresql://prod-db.internal:5432/app
    hikari:
      maximum-pool-size: 50           # override default
  logging:
    level:
      com.olhub: INFO

---
spring:
  config:
    activate:
      on-profile: prod & monitoring
  management:
    endpoints:
      web:
        exposure:
          include: health,metrics,prometheus

Boot pick document có on-profile match. Document đầu (no profile filter) luôn apply như default. Document sau override.

Khi nào dùng multi-document vs separate files:

ScenarioApproach
File ngắn (dưới 100 dòng), ít profile (2-3)Multi-document
File dài, nhiều profile, nhiều team chỉnhSeparate files
Dùng profile groupsSeparate files
Quick prototypeMulti-document

7. Inheritance giữa profile

Boot không có cơ chế "profile A extends profile B" trực tiếp. Pattern mô phỏng inheritance:

7.1 Common base + override

application.yml                     # base — moi profile inherit
application-prod-base.yml           # base prod (DB, cache config)
application-prod.yml                # prod-specific override
application-prod-canary.yml         # canary deployment override
# spring.profiles.active=prod-base,prod-canary
# Order: application.yml → prod-base → prod-canary

Profile sau override profile trước → cascade behavior như inheritance.

7.2 Include via property

# application-prod.yml
spring:
  profiles:
    include: prod-base, prod-monitoring     # auto include khi prod active
  datasource:
    url: ${PROD_DB_URL}

Activate prod → Boot tự include prod-baseprod-monitoring. Đây là cách viết explicit dependency giữa profile.

8. Pattern thực tế

8.1 Local + Docker Compose dev

# application.yml
spring:
  profiles:
    default: local

---
spring:
  config:
    activate:
      on-profile: local
  datasource:
    url: jdbc:postgresql://localhost:5432/dev
    username: dev_user
    password: dev_pass

---
spring:
  config:
    activate:
      on-profile: docker
  datasource:
    url: jdbc:postgresql://postgres:5432/app    # docker service name
    username: app
    password: app

Run local: mvn spring-boot:runlocal profile active. Run Docker Compose: SPRING_PROFILES_ACTIVE=docker docker-compose up.

8.2 Multi-region deployment

spring:
  profiles:
    group:
      asia-east: prod-base, prod-asia-east, prod-monitoring
      eu-west: prod-base, prod-eu-west, prod-monitoring
      us-east: prod-base, prod-us-east, prod-monitoring

3 region — mỗi region group 3 profile. application-prod-asia-east.yml chứa endpoint server Singapore, prod-eu-west.yml chứa endpoint Frankfurt. Common config (prod-base.yml) shared.

K8s deployment per region:

env:
  - name: SPRING_PROFILES_ACTIVE
    value: "asia-east"        # region-specific

8.3 Test profile cho integration test

@SpringBootTest
@ActiveProfiles("test")
class OrderServiceIntegrationTest {

    @Autowired OrderService service;

    @Test
    void placeOrderSuccess() { ... }
}
# application-test.yml — load tu src/test/resources
spring:
  datasource:
    url: jdbc:tc:postgresql:16:///testdb     # Testcontainers
  jpa:
    hibernate:
      ddl-auto: create-drop
logging:
  level:
    com.olhub: DEBUG

Test profile dùng Testcontainers (DB ephemeral), DEBUG log, ddl-auto=create-drop. Không lẫn với prod config.

8.4 Feature flag via profile

@Service
@Profile("feature-recommendation-v2")
public class RecommendationServiceV2 implements RecommendationService { ... }

@Service
@Profile("!feature-recommendation-v2")
public class RecommendationServiceV1 implements RecommendationService { ... }

Rollout v2: K8s deployment với SPRING_PROFILES_ACTIVE=prod,feature-recommendation-v2 cho 10% pod. 90% pod giữ v1. Spring Cloud Config + Kubernetes namespace label làm routing.

Pattern này stop-gap; LaunchDarkly/Unleash là feature flag platform purpose-built. Nhưng profile + @ConditionalOnProperty cũng đủ cho on/off feature đơn giản.

9. Vận hành production — secret management, blue/green, profile validation

Profile sai = config sai = behavior khác production. Section này cover safeguards.

9.1 Secret management — không hardcode

Anti-pattern: secret trong application-prod.yml:

# application-prod.yml — DO NOT
spring:
  datasource:
    password: prod-secret-123      # COMMIT GIT?!

Pattern enterprise:

  • HashiCorp Vault: spring-cloud-starter-vault-config.
  • AWS Secrets Manager: Spring Cloud AWS.
  • K8s Secret: mount as env var.
  • Sealed Secrets (Bitnami): encrypted in Git.

Property reference env var:

spring:
  datasource:
    password: ${DB_PASSWORD}      # tu env var, fail-fast neu missing

K8s manifest mount:

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: db-password

9.2 Profile validation startup — fail-fast

Force fail nếu thiếu critical property:

@Component
public class StartupValidator {
    @EventListener(ApplicationReadyEvent.class)
    public void validate(ApplicationReadyEvent event) {
        Environment env = event.getApplicationContext().getEnvironment();

        Profiles allowed = Profiles.of("dev", "test", "staging", "prod");
        if (!env.acceptsProfiles(allowed)) {
            throw new IllegalStateException(
                "Active profiles must include one of: dev/test/staging/prod. Got: " +
                Arrays.toString(env.getActiveProfiles())
            );
        }

        if (env.acceptsProfiles(Profiles.of("prod"))) {
            require(env, "spring.datasource.url");
            require(env, "spring.datasource.password");
        }
    }

    private void require(Environment env, String key) {
        if (env.getProperty(key) == null) {
            throw new IllegalStateException("Missing required property: " + key);
        }
    }
}

App refuse start nếu config invalid → fail fast tại deploy thay vì runtime bug ngầm.

9.3 Blue/green deployment

Pattern: deploy v2 với profile prod-v2, route 10% traffic, monitor metrics.

K8s manifest canary deployment:

spec:
  containers:
    - name: app
      env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod,canary"      # canary = 10% pod

Service mesh (Istio/Linkerd) route theo label. Profile canary enable extra logging, sampling rate cao hơn → so sánh metric giữa canary và prod ổn định.

9.4 Failure runbook

Mode 1 — Wrong profile active production:

  • Triệu chứng: app dùng config dev, connect DB localhost (down).
  • Diagnose: log "The following profiles are active: dev".
  • Remediate: re-deploy với SPRING_PROFILES_ACTIVE=prod. Monitor: alert nếu profile string không match expected pattern.

Mode 2 — Profile typo (vd prdo):

  • Triệu chứng: silent fall back default profile, bug ngầm.
  • Remediate: validation startup (section 9.2).

Mode 3 — Multiple profile config conflict:

  • Triệu chứng: config X có 2 value khác nhau giữa 2 profile cùng active.
  • Remediate: profile order intentional. Document priority. Verify với /actuator/configprops.

Mode 4 — Secret rotated, app vẫn dùng old:

  • Cause: K8s Secret update không trigger pod restart.
  • Remediate: rolling restart pod hoặc Spring Cloud Config refresh endpoint.

10. Pitfall tổng hợp

Nhầm 1: Profile name typo, app vẫn start.

java -jar app.jar --spring.profiles.active=prdo     # typo

Boot không có profile prdo → 0 profile active → fallback default → bug ngầm. ✅ Validate strict: kiểm tra log startup The following profiles are active: prdo — nếu thấy typo thì fix. Hoặc dùng Actuator /actuator/env check spring.profiles.active.

Nhầm 2: Bean không có @Profile nhưng giả định "chỉ active prod".

@Component        // KHONG @Profile → register o moi profile
public class CloudWatchExporter { ... }

Test profile cũng register → fail vì AWS credential không có local. ✅ Thêm @Profile("prod") explicit. Hoặc @ConditionalOnProperty cho fine-grained control.

Nhầm 3: Multiple profile active nhưng config conflict.

SPRING_PROFILES_ACTIVE=dev,prod

2 profile cùng define db.url → profile sau win (prod). Có thể intended hoặc accidental. Confusing. ✅ Tránh active conflicting profile. Hoặc rõ ràng "prod override dev" — đặt thứ tự intentional.

Nhầm 4: Quên application-\{profile\}.yml không tự load qua @PropertySource.

@Configuration
@PropertySource("classpath:custom.properties")
@Profile("prod")
public class ProdConfig { ... }

File custom.properties không phải convention application-X.yml → không tự profile-specific. Phải nhân bản: custom-prod.properties, custom-dev.properties. ✅ Dùng convention application-\{profile\}.yml + Boot tự handle. Tránh @PropertySource custom trừ khi cần.

Nhầm 5: @Profile("prod") @Component nhưng inject vào singleton common.

@Component @Profile("prod") class ProdHelper { ... }

@Service
public class CommonService {
    @Autowired ProdHelper helper;     // FAIL khi profile=dev (no bean)
}

✅ Inject qua interface + 2 implementation profile-specific:

@Component @Profile("prod") class ProdHelper implements Helper { ... }
@Component @Profile("!prod") class DevHelper implements Helper { ... }

@Autowired Helper helper;     // moi profile co implementation

Nhầm 6: Hardcode profile name strings rải rác code.

if (env.acceptsProfiles("prod")) { ... }     // hardcode string nhieu noi

✅ Define constant: public static final String PROD = "prod"; hoặc dùng enum. Refactor friendly.

Nhầm 7: @ActiveProfiles("test") trong test nhưng quên override application-test.yml. ✅ Profile test chỉ tag — phải có file application-test.yml (trong src/test/resources) hoặc bean @Profile("test") để có effect.

11. 📚 Deep Dive Spring Reference

📚 Tài liệu chính chủ

Spring Boot Reference docs:

Spring Framework docs:

Source:

Tool:

  • /actuator/env — runtime check active profile + property source.
  • IntelliJ "Spring Boot Profiles" tool window — quick switch profile.
  • --spring.profiles.active=... — override at run time.

Bài viết:

Ghi chú: profile là cơ chế đơn giản nhưng power. Pattern phổ biến là 1 profile cho env (prod), 1 cho region (asia-east), 1 cho feature (monitoring). Multi-active profile cho phép orthogonal concern. Đừng over-engineer — bắt đầu với 3-4 profile (dev, test, staging, prod), thêm khi cần.

12. Tóm tắt

  • Profile là named tag gắn vào bean/config — toggle theo môi trường.
  • 5 cách activate: property spring.profiles.active, JVM -D, env var SPRING_PROFILES_ACTIVE, programmatic setAdditionalProfiles, test @ActiveProfiles.
  • Multi-active: dev,monitoring,asia-east — Boot apply theo thứ tự, sau override trước.
  • @Profile đặt được trên @Bean, @Component, @Configuration class.
  • Profile expression (!, |, &): @Profile("!dev"), @Profile("prod | staging"), @Profile("prod & monitoring").
  • Profile groups (Boot 2.4+) gom logic: production = prod + prod-db + prod-cache. Tách concern thành nhiều file.
  • Default profile default: bean register khi không profile nào active. spring.profiles.default set fallback.
  • Multi-document YAML (Boot 2.4+): nhiều profile trong 1 file qua --- + spring.config.activate.on-profile.
  • Inheritance mô phỏng qua spring.profiles.include hoặc cascade activation order.
  • Pattern thực tế: 1 profile cho env, 1 cho region, 1 cho feature. Multi-tenant qua profile group.
  • Test profile + Testcontainers: @ActiveProfiles("test") + application-test.yml chứa Testcontainers JDBC URL.
  • Feature flag đơn giản qua @Profile("feature-X"). Production rollout dùng LaunchDarkly/Unleash purpose-built.

13. Tự kiểm tra

Tự kiểm tra
Q1
Bean @Component @Profile("prod") được inject vào @Service không có annotation profile. Active profile = "dev". Điều gì xảy ra tại startup?

App fail to start với NoSuchBeanDefinitionException.

Lý do:

  1. @Service không có @Profile → register ở mọi profile (bao gồm dev).
  2. Service constructor cần inject 1 bean @Profile("prod") — nhưng profile dev active, bean prod skip.
  3. Spring resolve dependency: tìm bean type → không có → throw.

Cách fix — 3 lựa chọn:

  1. Inject qua interface + 2 implementation:
    public interface MetricsExporter { ... }
    @Component @Profile("prod") class CloudWatchExporter implements MetricsExporter { ... }
    @Component @Profile("!prod") class ConsoleExporter implements MetricsExporter { ... }
    
    @Service
    public class App {
      @Autowired MetricsExporter exporter;     // mọi profile có implementation
    }
  2. Optional dependency:
    @Autowired(required = false)
    private MetricsExporter exporter;     // null nếu profile != prod
    Code phải null-check.
  3. ObjectProvider:
    @Autowired ObjectProvider<MetricsExporter> exporterProvider;
    
    public void exportIfAvailable() {
      exporterProvider.ifAvailable(e -> e.export(...));
    }

Best practice: cách 1 — luôn có implementation cho mọi profile. App không cần null-check, code business clean.

Q2
Profile expression @Profile("prod & monitoring") — bean register khi nào? Khi nào không?

Bean register khi cả 2 profile prodmonitoring đều active.

Operator profile expression:

  • !X: NOT — profile X không active.
  • X | Y: OR — X HOẶC Y active.
  • X & Y: AND — X VÀ Y cùng active (Spring 6 / Boot 5.1+).
  • {X, Y}: array = OR (legacy syntax).

Bảng truth table cho "prod & monitoring":

Active profilesBean register?
prod❌ Không (thiếu monitoring)
monitoring❌ Không (thiếu prod)
prod, monitoring✅ Có
prod, monitoring, asia-east✅ Có (đủ 2 profile cần)
dev, monitoring❌ Không (thiếu prod)
(không profile)❌ Không

Use case thực tế: Prometheus exporter chỉ cần khi prod + có monitoring stack. Dev profile không cần (overhead). Profile expression cho phép logic AND mà không phải tách 2 bean.

Cách activate prod + monitoring:

# Cach 1: list trong env
SPRING_PROFILES_ACTIVE=prod,monitoring

# Cach 2: profile group
spring:
profiles:
  group:
    production: prod, monitoring
Q3
Đoạn YAML sau có gì hay? Khi nào nên dùng pattern này thay vì tách thành nhiều file?
spring:
application:
  name: order-service

---
spring:
config:
  activate:
    on-profile: dev
datasource:
  url: jdbc:postgresql://localhost/dev

---
spring:
config:
  activate:
    on-profile: prod
datasource:
  url: ${PROD_DB_URL}

Cái hay: multi-document YAML (Boot 2.4+) — 3 document trong 1 file ngăn cách bằng ---.

Cách hoạt động:

  1. Document 1 (no profile filter): apply mọi profile — common config.
  2. Document 2 (on-profile: dev): apply chỉ khi dev active.
  3. Document 3 (on-profile: prod): apply chỉ khi prod active.

Boot pick document có on-profile match active profile, merge với document 1.

Khi nào dùng multi-document vs separate files:

Tình huốngApproach
File ngắn (<100 dòng), 2-3 profileMulti-document — 1 file, dễ thấy overview
File dài, 5+ profile, nhiều team chỉnhSeparate files — tách concern, merge conflict ít hơn
Dùng profile groupsSeparate files — group reference file
Quick prototype / demoMulti-document — không phải tạo nhiều file
Production app stableSeparate files — review per-profile dễ

Pros multi-document:

  • Single source of truth — open 1 file thấy toàn cảnh.
  • Đảm bảo cấu trúc nhất quán (cùng key parent spring.config.activate.on-profile).
  • Dễ diff giữa profile (cùng file → IDE highlight diff).

Cons multi-document:

  • File phình to khi nhiều profile.
  • Merge conflict khi nhiều dev cùng sửa profile khác nhau.
  • Search/grep khó hơn (cần match profile context).

Pattern lai: giữ application.yml với common config + multi-document cho profile nhỏ. File lớn (vd application-prod.yml với 200 dòng) tách riêng.

Q4
Bạn deploy app ở 3 region: asia-east, eu-west, us-east. Mỗi region có DB endpoint riêng + S3 bucket riêng + tracing endpoint riêng. Common config (security, jackson) giống nhau. Thiết kế profile layout thế nào để DRY?

Profile group + region-specific files:

# application.yml — common cho moi profile
spring:
application:
  name: order-service
jackson:
  serialization:
    write-dates-as-timestamps: false
security:
  oauth2:
    resource-server:
      jwt:
        issuer-uri: ${JWT_ISSUER}

profiles:
  group:
    asia-east: prod-base, prod-asia-east
    eu-west: prod-base, prod-eu-west
    us-east: prod-base, prod-us-east
# application-prod-base.yml — config common cho prod (moi region)
spring:
datasource:
  hikari:
    maximum-pool-size: 50
jpa:
  hibernate:
    ddl-auto: validate
logging:
level:
  com.olhub: INFO
# application-prod-asia-east.yml — region-specific
spring:
datasource:
  url: jdbc:postgresql://prod-db.asia-east.internal/app
cloud:
  aws:
    s3:
      endpoint: https://s3.ap-southeast-1.amazonaws.com
management:
tracing:
  sampling:
    probability: 0.1
zipkin:
  tracing:
    endpoint: https://tracing.asia-east.internal/api/v2/spans
# application-prod-eu-west.yml
spring:
datasource:
  url: jdbc:postgresql://prod-db.eu-west.internal/app
cloud:
  aws:
    s3:
      endpoint: https://s3.eu-west-1.amazonaws.com
management:
zipkin:
  tracing:
    endpoint: https://tracing.eu-west.internal/api/v2/spans

K8s deployment per region:

# K8s asia-east cluster
env:
- name: SPRING_PROFILES_ACTIVE
  value: "asia-east"

# K8s eu-west cluster
env:
- name: SPRING_PROFILES_ACTIVE
  value: "eu-west"

Lợi ích:

  • DRY: common (security, jackson) trong application.yml. Common prod (pool, JPA) trong prod-base.yml. Chỉ region-specific (DB URL, S3 endpoint, tracing) trong file region.
  • Easy review: mở prod-eu-west.yml thấy ngay khác biệt vs region khác.
  • Add region mới: tạo prod-ap-south.yml + thêm vào group ap-south. Không phải duplicate base config.
  • Rollback dễ: bug in 1 region không affect region khác (file riêng biệt).

Anti-pattern (đừng làm): 3 file application-asia-east.yml, application-eu-west.yml, application-us-east.yml mỗi file 200 dòng duplicate 80%. Khi cần update common rule → sửa 3 chỗ → dễ inconsistent.

Q5
Profile default khác profile không có annotation ở chỗ nào? Cho ví dụ minh hoạ.

Khác biệt:

  • Bean không @Profile: register ở mọi profile (bao gồm khi không có active profile).
  • Bean @Profile("default"): register CHỈ KHI không có active profile nào.

Ví dụ minh hoạ:

@Component
public class CommonService { ... }              // case 1

@Component
@Profile("default")
public class FallbackService { ... }             // case 2

@Component
@Profile("dev")
public class DevHelper { ... }                   // case 3

Bảng register theo profile active:

spring.profiles.activeCommonServiceFallbackServiceDevHelper
(empty)
dev
prod
dev,prod

Use case @Profile("default"):

  • Fallback config khi developer chạy local không set profile: 1 instance H2 in-memory thay vì kết nối real DB.
  • Demo mode: app chạy với mock data khi không config gì.
  • Initial setup wizard: bean tạo data sample khi app lần đầu chạy fresh.

Pattern phổ biến:

# application.yml
spring:
profiles:
  default: dev      # neu khong active gi, fallback 'dev'

Set spring.profiles.default = profile dùng khi spring.profiles.active empty. Lúc này @Profile("default") bean **không register** (vì đã có dev active).

Q6
Bạn rollout feature mới qua profile feature-recommendation-v2 cho 10% pod ở K8s. Strategy nào setup K8s deployment + Spring profile?

Approach: Canary deployment với 2 K8s deployment chia traffic.

# K8s deployment chinh - 90% pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 9                       # 90% capacity
template:
  spec:
    containers:
      - name: app
        env:
          - name: SPRING_PROFILES_ACTIVE
            value: "prod"
# K8s canary deployment - 10% pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service-canary
spec:
replicas: 1                       # 10% capacity
template:
  spec:
    containers:
      - name: app
        env:
          - name: SPRING_PROFILES_ACTIVE
            value: "prod,feature-recommendation-v2"     # them feature flag profile
# Service span ca 2 deployment qua label
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
  app: order-service              # match ca 2 deployment
ports:
  - port: 80
    targetPort: 8080

Spring code:

public interface RecommendationService {
  List<Product> recommend(User user);
}

@Service
@Profile("feature-recommendation-v2")
public class RecommendationServiceV2 implements RecommendationService {
  // ML-based, expensive
}

@Service
@Profile("!feature-recommendation-v2")
public class RecommendationServiceV1 implements RecommendationService {
  // Rule-based, fast
}

Service inject RecommendationService — Boot pick V1 hoặc V2 tuỳ profile của pod.

Rollout dần:

  1. Day 1: Canary 1 pod (10%). Monitor metrics, error rate.
  2. Day 3: Tăng canary lên 3 pod (30%). Tiếp tục monitor.
  3. Day 7: Tăng lên 5 pod (50%). A/B test result.
  4. Day 14: Tăng lên 9 pod (90%). Bỏ deployment chính.
  5. Day 21: 100% pod chạy V2. Xoá feature-recommendation-v2 profile, mark V1 deprecated.

Cảnh báo: profile-based feature flag là stop-gap. Production-grade feature flag dùng LaunchDarkly/Unleash/FeatureHub:

  • Toggle runtime, không restart pod.
  • User segmentation (10% user thấy V2, không phải 10% pod).
  • A/B test với metrics tracking.
  • Kill switch nhanh chóng.

Profile chỉ phù hợp cho rollout simple (vd toàn site dùng V2 sau 1 tuần). Phức tạp hơn → tool chuyên dụng.

Bài tiếp theo: Logging — Logback default, structured logging, log level dynamic

Bài này có giúp bạn hiểu bản chất không?

Bình luận (0)

Đang tải...