Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

Commit b8f16e4

Browse files
committed
Merge branch 'main' into claude/issue-60-20260309-1819
2 parents 0bbae19 + 122ce0f commit b8f16e4

27 files changed

Lines changed: 570 additions & 29 deletions

File tree

examples/webflux/actuator-endpoint/README.ja.md

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ Spring WebFlux アプリケーションにおける、Spring Boot Actuator エ
44

55
## 機能
66

7-
- `GET /actuator/endpoint-gates` -- 全フィーチャーフラグの一覧取得
8-
- `GET /actuator/endpoint-gates/{gateId}` -- 単一フィーチャーフラグの取得
9-
- `POST /actuator/endpoint-gates` -- フィーチャーフラグの更新(有効状態および任意のロールアウト
10-
- `DELETE /actuator/endpoint-gates/{gateId}` -- フィーチャーフラグの削除
11-
- `EndpointGateChangedEvent` / `EndpointGateRemovedEvent` -- `@EventListener` によるイベントハンドリング
7+
- `GET /actuator/endpoint-gates` — 全ゲート一覧取得
8+
- `GET /actuator/endpoint-gates/{gateId}` — 単一ゲート取得
9+
- `POST /actuator/endpoint-gates` — ゲートの更新(有効/無効・ロールアウト割合・スケジュール
10+
- `DELETE /actuator/endpoint-gates/{gateId}` — ゲートの削除
11+
- `EndpointGateChangedEvent` / `EndpointGateRemovedEvent` `@EventListener` によるイベントハンドリング
1212

1313
## 設定
1414

1515
```yaml
16-
endpoint-gates:
17-
features:
16+
endpoint-gate:
17+
gates:
1818
demo-feature:
1919
enabled: true
2020
rollout: 80
@@ -34,17 +34,42 @@ management:
3434
`MutableInMemoryRolloutPercentageProvider` を自動的に登録します。
3535
これらは `@ConditionalOnMissingBean` により `webflux` モジュールのデフォルト(読み取り専用)プロバイダーを置き換えます。
3636

37-
`EndpointGateEndpoint` は `/actuator/endpoint-gates` に公開され、アプリケーションを再起動せずにランタイムでフィーチャーフラグへの完全なCRUD操作を可能にします。
37+
内部的には `EndpointGateEndpoint` ではなく `ReactiveEndpointGateEndpoint` が使用されます。
38+
API のリクエスト/レスポンス形式は WebMVC 版と同一です。
39+
40+
`EndpointGateEndpoint` は `/actuator/endpoint-gates` に公開され、アプリケーションを再起動せずにランタイムでゲートへの完全なCRUD操作を可能にします。
3841

3942
書き込みまたは削除のたびにイベントが発行されます:
4043
- `EndpointGateChangedEvent`: `gateId()`、`enabled()`、`rolloutPercentage()`(変更されていない場合は null)
41-
- `EndpointGateRemovedEvent`: `gateId()`フラグが実際に存在した場合のみ発行
44+
- `EndpointGateRemovedEvent`: `gateId()`ゲートが実際に存在した場合のみ発行
4245

4346
## デモ手順
4447

4548
1. アプリケーションを起動する
46-
2. 全フラグを一覧表示: `GET /actuator/endpoint-gates`
47-
3. 単一フラグを取得: `GET /actuator/endpoint-gates/demo-feature`
49+
2. 全ゲートを一覧表示: `GET /actuator/endpoint-gates`
50+
3. 単一ゲートを取得: `GET /actuator/endpoint-gates/demo-feature`
4851
4. another-feature を有効化: `POST /actuator/endpoint-gates` にボディ `{"gateId":"another-feature","enabled":true}` を送信
4952
5. another-feature を削除: `DELETE /actuator/endpoint-gates/another-feature`
50-
6. コンソールログで発行されたイベントを確認する
53+
6. demo-feature にスケジュールを設定する:
54+
```bash
55+
curl -X POST http://localhost:8080/actuator/endpoint-gates \
56+
-H "Content-Type: application/json" \
57+
-d '{
58+
"gateId": "demo-feature",
59+
"enabled": true,
60+
"scheduleStart": "2026-03-10T09:00:00",
61+
"scheduleEnd": "2026-03-10T18:00:00",
62+
"scheduleTimezone": "Asia/Tokyo"
63+
}'
64+
```
65+
7. demo-feature のスケジュールを削除する:
66+
```bash
67+
curl -X POST http://localhost:8080/actuator/endpoint-gates \
68+
-H "Content-Type: application/json" \
69+
-d '{
70+
"gateId": "demo-feature",
71+
"enabled": true,
72+
"removeSchedule": true
73+
}'
74+
```
75+
8. コンソールログで発行されたイベントを確認する

examples/webflux/actuator-endpoint/README.md

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ This example demonstrates runtime endpoint gate management via the Spring Boot A
44

55
## Features
66

7-
- `GET /actuator/endpoint-gates` -- list all endpoint gates
8-
- `GET /actuator/endpoint-gates/{gateId}` -- get a single endpoint gate
9-
- `POST /actuator/endpoint-gates` -- update a endpoint gate (enabled state and optional rollout)
10-
- `DELETE /actuator/endpoint-gates/{gateId}` -- remove a endpoint gate
11-
- `EndpointGateChangedEvent` / `EndpointGateRemovedEvent` -- `@EventListener` handling
7+
- `GET /actuator/endpoint-gates` list all endpoint gates
8+
- `GET /actuator/endpoint-gates/{gateId}` get a single endpoint gate
9+
- `POST /actuator/endpoint-gates` update an endpoint gate (enabled state, rollout, and schedule)
10+
- `DELETE /actuator/endpoint-gates/{gateId}` remove an endpoint gate
11+
- `EndpointGateChangedEvent` / `EndpointGateRemovedEvent` `@EventListener` handling
1212

1313
## Configuration
1414

1515
```yaml
16-
endpoint-gates:
17-
features:
16+
endpoint-gate:
17+
gates:
1818
demo-feature:
1919
enabled: true
2020
rollout: 80
@@ -34,18 +34,43 @@ The `actuator` module automatically registers `MutableInMemoryEndpointGateProvid
3434
`MutableInMemoryRolloutPercentageProvider`, which replace the default read-only providers
3535
from the `webflux` module via `@ConditionalOnMissingBean`.
3636

37+
Internally, `ReactiveEndpointGateEndpoint` is used instead of `EndpointGateEndpoint`.
38+
The request/response format is identical to the WebMVC version.
39+
3740
The `EndpointGateEndpoint` is exposed at `/actuator/endpoint-gates` and allows full CRUD
3841
operations on endpoint gates at runtime without restarting the application.
3942

4043
Events are published on every write or delete:
4144
- `EndpointGateChangedEvent`: `gateId()`, `enabled()`, `rolloutPercentage()` (null if not changed)
42-
- `EndpointGateRemovedEvent`: `gateId()` (only fired when the flag actually existed)
45+
- `EndpointGateRemovedEvent`: `gateId()` (only fired when the gate actually existed)
4346

4447
## Demo steps
4548

4649
1. Start the application
47-
2. List all flags: `GET /actuator/endpoint-gates`
48-
3. Get a single flag: `GET /actuator/endpoint-gates/demo-feature`
50+
2. List all gates: `GET /actuator/endpoint-gates`
51+
3. Get a single gate: `GET /actuator/endpoint-gates/demo-feature`
4952
4. Enable another-feature: `POST /actuator/endpoint-gates` with body `{"gateId":"another-feature","enabled":true}`
5053
5. Delete another-feature: `DELETE /actuator/endpoint-gates/another-feature`
51-
6. Check the console log for published events
54+
6. Set a schedule on demo-feature:
55+
```bash
56+
curl -X POST http://localhost:8080/actuator/endpoint-gates \
57+
-H "Content-Type: application/json" \
58+
-d '{
59+
"gateId": "demo-feature",
60+
"enabled": true,
61+
"scheduleStart": "2026-03-10T09:00:00",
62+
"scheduleEnd": "2026-03-10T18:00:00",
63+
"scheduleTimezone": "Asia/Tokyo"
64+
}'
65+
```
66+
7. Remove the schedule from demo-feature:
67+
```bash
68+
curl -X POST http://localhost:8080/actuator/endpoint-gates \
69+
-H "Content-Type: application/json" \
70+
-d '{
71+
"gateId": "demo-feature",
72+
"enabled": true,
73+
"removeSchedule": true
74+
}'
75+
```
76+
8. Check the console log for published events
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
id("example-base")
3+
id("spotless-java")
4+
}
5+
6+
dependencies {
7+
implementation(project(":spring:spring-webflux"))
8+
implementation(libs.spring.boot.starter.webflux)
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.brightroom.example.condition;
2+
3+
import net.brightroom.endpointgate.core.annotation.EndpointGate;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
import reactor.core.publisher.Mono;
7+
8+
/** Role-based condition example. Requires {@code X-User-Role: admin} header to access. */
9+
@RestController
10+
public class AdminController {
11+
12+
/**
13+
* Returns admin dashboard content when the {@code X-User-Role} header equals {@code admin}.
14+
*
15+
* @return admin dashboard response
16+
*/
17+
@EndpointGate("admin-panel")
18+
@GetMapping("/api/admin/dashboard")
19+
public Mono<String> adminDashboard() {
20+
return Mono.just("Admin dashboard");
21+
}
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.brightroom.example.condition;
2+
3+
import net.brightroom.endpointgate.core.annotation.EndpointGate;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
import reactor.core.publisher.Mono;
7+
8+
/**
9+
* Query parameter-based condition example. Requires {@code beta=true} query parameter to access.
10+
*/
11+
@RestController
12+
public class BetaFeatureController {
13+
14+
/**
15+
* Returns beta feature content when the {@code beta} query parameter equals {@code true}.
16+
*
17+
* @return beta feature response
18+
*/
19+
@EndpointGate("beta-feature")
20+
@GetMapping("/api/beta/feature")
21+
public Mono<String> betaFeature() {
22+
return Mono.just("Beta feature enabled");
23+
}
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package net.brightroom.example.condition;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
/** Entry point for the WebFlux condition example application. */
7+
@SpringBootApplication
8+
public class ConditionExampleApplication {
9+
10+
/**
11+
* Starts the application.
12+
*
13+
* @param args command-line arguments
14+
*/
15+
public static void main(String[] args) {
16+
SpringApplication.run(ConditionExampleApplication.class, args);
17+
}
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.brightroom.example.condition;
2+
3+
import net.brightroom.endpointgate.core.annotation.EndpointGate;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
import reactor.core.publisher.Mono;
7+
8+
/** Header-based condition example. Requires {@code X-Internal-Token} header to access. */
9+
@RestController
10+
public class InternalApiController {
11+
12+
/**
13+
* Returns internal data when the {@code X-Internal-Token} header matches the expected value.
14+
*
15+
* @return internal data response
16+
*/
17+
@EndpointGate("internal-api")
18+
@GetMapping("/api/internal/data")
19+
public Mono<String> getData() {
20+
return Mono.just("Internal data");
21+
}
22+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
endpoint-gate:
2+
condition:
3+
fail-on-error: false
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
endpoint-gate:
2+
condition:
3+
fail-on-error: true
4+
gates:
5+
internal-api:
6+
enabled: true
7+
condition: "headers['X-Internal-Token'] == 'secret-token'"
8+
beta-feature:
9+
enabled: true
10+
condition: "params['beta'] == 'true'"
11+
admin-panel:
12+
enabled: true
13+
condition: "headers['X-User-Role'] == 'admin'"

examples/webflux/functional-endpoint/src/main/java/net/brightroom/example/functional/FeatureHandler.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,12 @@ public Mono<ServerResponse> beta(ServerRequest request) {
1919
public Mono<ServerResponse> experimental(ServerRequest request) {
2020
return ServerResponse.ok().bodyValue("experimental-api: this endpoint is under development.");
2121
}
22+
23+
public Mono<ServerResponse> internal(ServerRequest request) {
24+
return ServerResponse.ok().bodyValue("internal-api: this endpoint is for internal use only.");
25+
}
26+
27+
public Mono<ServerResponse> preview(ServerRequest request) {
28+
return ServerResponse.ok().bodyValue("preview-feature: you are in the preview group!");
29+
}
2230
}

0 commit comments

Comments
 (0)