Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,33 @@

과거에는 `dev`/`main` 두 개의 장수 branch를 cherry-pick으로 동기화하는 방식이었으나, 두 branch가 내용상 계속 어긋나는 문제(cherry-pick hell)가 반복되어 폐기했다. staging 배포도 `main` push를 기준으로 트리거된다.

### 🚩 Feature Flag

미완성/위험도 있는 기능을 브랜치에 오래 묵히지 않고 `main`에 먼저 merge한 뒤, 배포 이후 원하는 시점에 켤 수 있게 하는 최소 구현이다. (`org.runnect.server.config.featureflag`)

```yaml
# application.yml (environment별로 값이 다를 수 있음)
feature-flags:
flags:
new-run-summary: false
```

```java
@RequiredArgsConstructor
public class SomeService {
private final FeatureFlags featureFlags;

public void doSomething() {
if (featureFlags.isEnabled("new-run-summary")) {
// 새 로직
}
// 기존 로직
}
}
```

yml에 키가 없으면 기본값은 비활성(`false`)이다.

</aside>
<hr>
</br>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.runnect.server.config.featureflag;

import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* application.yml의 feature-flags.flags.{key}: true/false 값을 그대로 바인딩한다.
* yml에 없는 키는 FeatureFlags에서 false(비활성)로 취급한다.
*/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "feature-flags")
public class FeatureFlagProperties {

private Map<String, Boolean> flags = new HashMap<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.runnect.server.config.featureflag;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
* 미완성/위험도가 있는 기능을 main에 먼저 merge하고 배포 이후에 켤 수 있게 하는
* 트렁크 기반 개발용 최소 기능 플래그. 값은 environment별 application.yml의
* feature-flags.flags.{key}로 관리한다 (예: feature-flags.flags.new-run-summary: true).
*/
@Component
@RequiredArgsConstructor
public class FeatureFlags {

private final FeatureFlagProperties properties;

public boolean isEnabled(String key) {
return properties.getFlags().getOrDefault(key, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.runnect.server.config.featureflag;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Map;
import org.junit.jupiter.api.Test;

class FeatureFlagsTest {

@Test
void yml에_true로_설정된_플래그는_활성이다() {
FeatureFlagProperties properties = new FeatureFlagProperties();
properties.setFlags(Map.of("new-run-summary", true));
FeatureFlags featureFlags = new FeatureFlags(properties);

assertThat(featureFlags.isEnabled("new-run-summary")).isTrue();
}

@Test
void yml에_false로_설정된_플래그는_비활성이다() {
FeatureFlagProperties properties = new FeatureFlagProperties();
properties.setFlags(Map.of("new-run-summary", false));
FeatureFlags featureFlags = new FeatureFlags(properties);

assertThat(featureFlags.isEnabled("new-run-summary")).isFalse();
}

@Test
void yml에_정의되지_않은_플래그는_기본값으로_비활성이다() {
FeatureFlagProperties properties = new FeatureFlagProperties();
FeatureFlags featureFlags = new FeatureFlags(properties);

assertThat(featureFlags.isEnabled("존재하지-않는-플래그")).isFalse();
}
}
Loading