Skip to content

Commit ce10c5c

Browse files
authored
Merge pull request #4 from OpenFluxGate/development/mongo-adaptor
development. fluxgate-mongo-adaptor with fluxgate-testkit
2 parents f2e8b96 + d2d838a commit ce10c5c

18 files changed

Lines changed: 2603 additions & 561 deletions

File tree

README.md

Lines changed: 3 additions & 552 deletions
Large diffs are not rendered by default.

fluxgate-core/README.md

Lines changed: 552 additions & 0 deletions
Large diffs are not rendered by default.

fluxgate-core/src/main/java/org/fluxgate/core/config/RateLimitRule.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@
77

88
/**
99
* Core configuration object describing a rate limit rule.
10-
* <p>
1110
* This rule is intentionally storage-agnostic and engine-agnostic.
1211
* It does not depend on Redis, MongoDB, HTTP frameworks, etc.
13-
* Later, adapters will translate this rule into Bucket4j configuration.
12+
* Adapters will translate this rule into engine-specific configuration (e.g. Bucket4j).
1413
*/
1514
public final class RateLimitRule {
1615

1716
private final String id;
1817
private final String name;
1918
private final boolean enabled;
20-
2119
private final LimitScope scope;
20+
2221
/**
2322
* Identifier of the key strategy (for example "ip", "userId", "apiKey").
2423
* The actual implementation will be resolved via SPI.
@@ -32,14 +31,23 @@ public final class RateLimitRule {
3231

3332
private final List<RateLimitBand> bands;
3433

34+
/**
35+
* Optional: logical rule set identifier this rule belongs to.
36+
* Used mainly for logging / metrics in adapters.
37+
*/
38+
private final String ruleSetId;
39+
3540
private RateLimitRule(Builder builder) {
3641
this.id = Objects.requireNonNull(builder.id, "id must not be null");
3742
this.name = builder.name != null ? builder.name : builder.id;
3843
this.enabled = builder.enabled;
3944
this.scope = Objects.requireNonNull(builder.scope, "scope must not be null");
4045
this.keyStrategyId = Objects.requireNonNull(builder.keyStrategyId, "keyStrategyId must not be null");
41-
this.onLimitExceedPolicy = Objects.requireNonNull(builder.onLimitExceedPolicy,
42-
"onLimitExceedPolicy must not be null");
46+
this.onLimitExceedPolicy = Objects.requireNonNull(
47+
builder.onLimitExceedPolicy,
48+
"onLimitExceedPolicy must not be null"
49+
);
50+
this.ruleSetId = builder.ruleSetId; // nullable
4351

4452
if (builder.bands.isEmpty()) {
4553
throw new IllegalArgumentException("At least one RateLimitBand must be configured");
@@ -75,6 +83,13 @@ public List<RateLimitBand> getBands() {
7583
return Collections.unmodifiableList(bands);
7684
}
7785

86+
/**
87+
* May be null if this rule is not associated with a specific rule set.
88+
*/
89+
public String getRuleSetIdOrNull() {
90+
return ruleSetId;
91+
}
92+
7893
public static Builder builder(String id) {
7994
return new Builder(id);
8095
}
@@ -87,6 +102,7 @@ public static final class Builder {
87102
private String keyStrategyId = "apiKey"; // sensible default
88103
private OnLimitExceedPolicy onLimitExceedPolicy = OnLimitExceedPolicy.REJECT_REQUEST;
89104
private final List<RateLimitBand> bands = new ArrayList<>();
105+
private String ruleSetId; // optional
90106

91107
private Builder(String id) {
92108
this.id = Objects.requireNonNull(id, "id must not be null");
@@ -125,6 +141,15 @@ public Builder addBand(RateLimitBand band) {
125141
return this;
126142
}
127143

144+
/**
145+
* Optional: set the logical rule set id this rule belongs to.
146+
* Only used for observability (logging/metrics).
147+
*/
148+
public Builder ruleSetId(String ruleSetId) {
149+
this.ruleSetId = ruleSetId;
150+
return this;
151+
}
152+
128153
public RateLimitRule build() {
129154
return new RateLimitRule(this);
130155
}
@@ -140,6 +165,7 @@ public String toString() {
140165
", keyStrategyId='" + keyStrategyId + '\'' +
141166
", onLimitExceedPolicy=" + onLimitExceedPolicy +
142167
", bands=" + bands +
168+
", ruleSetId='" + ruleSetId + '\'' +
143169
'}';
144170
}
145-
}
171+
}

fluxgate-mongo-adapter/README.md

8.47 KB
Binary file not shown.

fluxgate-mongo-adapter/pom.xml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="
5+
http://maven.apache.org/POM/4.0.0
6+
https://maven.apache.org/xsd/maven-4.0.0.xsd">
7+
8+
<modelVersion>4.0.0</modelVersion>
9+
10+
<parent>
11+
<groupId>org.fluxgate</groupId>
12+
<artifactId>fluxgate</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
</parent>
15+
16+
<artifactId>fluxgate-mongo-adapter</artifactId>
17+
<packaging>jar</packaging>
18+
19+
<name>FluxGate MongoDB Adapter</name>
20+
<description>MongoDB adapter for FluxGate distributed rate limiting</description>
21+
22+
<properties>
23+
<mongodb.driver.version>5.2.1</mongodb.driver.version>
24+
</properties>
25+
26+
<dependencies>
27+
<!-- FluxGate Core -->
28+
<dependency>
29+
<groupId>org.fluxgate</groupId>
30+
<artifactId>fluxgate-core</artifactId>
31+
<version>${project.version}</version>
32+
</dependency>
33+
34+
<!-- MongoDB Driver -->
35+
<dependency>
36+
<groupId>org.mongodb</groupId>
37+
<artifactId>mongodb-driver-sync</artifactId>
38+
<version>${mongodb.driver.version}</version>
39+
</dependency>
40+
41+
<!-- Logging API (implementation to be provided by consumer) -->
42+
<dependency>
43+
<groupId>org.slf4j</groupId>
44+
<artifactId>slf4j-api</artifactId>
45+
</dependency>
46+
47+
<!-- Test dependencies -->
48+
<dependency>
49+
<groupId>org.junit.jupiter</groupId>
50+
<artifactId>junit-jupiter</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>org.assertj</groupId>
56+
<artifactId>assertj-core</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
</dependencies>
60+
61+
<build>
62+
<plugins>
63+
<!-- Inherit plugin configurations from parent -->
64+
<plugin>
65+
<groupId>org.apache.maven.plugins</groupId>
66+
<artifactId>maven-compiler-plugin</artifactId>
67+
</plugin>
68+
69+
<plugin>
70+
<groupId>org.apache.maven.plugins</groupId>
71+
<artifactId>maven-jar-plugin</artifactId>
72+
</plugin>
73+
74+
<plugin>
75+
<groupId>org.apache.maven.plugins</groupId>
76+
<artifactId>maven-source-plugin</artifactId>
77+
</plugin>
78+
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-javadoc-plugin</artifactId>
82+
</plugin>
83+
84+
<plugin>
85+
<groupId>org.apache.maven.plugins</groupId>
86+
<artifactId>maven-surefire-plugin</artifactId>
87+
</plugin>
88+
89+
<plugin>
90+
<groupId>org.apache.maven.plugins</groupId>
91+
<artifactId>maven-deploy-plugin</artifactId>
92+
</plugin>
93+
</plugins>
94+
</build>
95+
96+
</project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.fluxgate.adapter.mongo.config;
2+
3+
import com.mongodb.client.MongoClient;
4+
import com.mongodb.client.MongoCollection;
5+
import com.mongodb.client.MongoDatabase;
6+
import org.bson.Document;
7+
import org.fluxgate.adapter.mongo.repository.MongoRateLimitRuleRepository;
8+
import org.fluxgate.adapter.mongo.rule.MongoRuleSetProvider;
9+
import org.fluxgate.core.key.KeyResolver;
10+
import org.fluxgate.core.spi.RateLimitRuleSetProvider;
11+
12+
public final class FluxgateMongoConfig {
13+
14+
private final MongoDatabase database;
15+
private final String ruleCollectionName;
16+
private final String eventCollectionName;
17+
18+
public FluxgateMongoConfig(MongoClient client,
19+
String databaseName,
20+
String ruleCollectionName,
21+
String eventCollectionName) {
22+
this.database = client.getDatabase(databaseName);
23+
this.ruleCollectionName = ruleCollectionName;
24+
this.eventCollectionName = eventCollectionName;
25+
}
26+
27+
public MongoCollection<Document> ruleCollection() {
28+
return database.getCollection(ruleCollectionName);
29+
}
30+
31+
public MongoCollection<Document> eventCollection() {
32+
return database.getCollection(eventCollectionName);
33+
}
34+
35+
public MongoRateLimitRuleRepository ruleRepository() {
36+
return new MongoRateLimitRuleRepository(ruleCollection());
37+
}
38+
39+
public RateLimitRuleSetProvider ruleSetProvider(KeyResolver keyResolver) {
40+
return new MongoRuleSetProvider(ruleRepository(), keyResolver);
41+
}
42+
43+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package org.fluxgate.adapter.mongo.converter;
2+
3+
import org.bson.Document;
4+
import org.fluxgate.adapter.mongo.model.RateLimitBandDocument;
5+
import org.fluxgate.adapter.mongo.model.RateLimitRuleDocument;
6+
import org.fluxgate.core.config.LimitScope;
7+
import org.fluxgate.core.config.OnLimitExceedPolicy;
8+
import org.fluxgate.core.config.RateLimitBand;
9+
import org.fluxgate.core.config.RateLimitRule;
10+
11+
import java.time.Duration;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
public final class RateLimitRuleMongoConverter {
16+
17+
private RateLimitRuleMongoConverter() {
18+
}
19+
20+
/* ========= Domain <-> DTO ========= */
21+
22+
public static RateLimitRuleDocument toDto(RateLimitRule rule) {
23+
List<RateLimitBandDocument> bandDocs = new ArrayList<>();
24+
for (RateLimitBand band : rule.getBands()) {
25+
bandDocs.add(toDto(band));
26+
}
27+
28+
return new RateLimitRuleDocument(
29+
rule.getId(),
30+
rule.getName(),
31+
rule.isEnabled(),
32+
rule.getScope(),
33+
rule.getKeyStrategyId(),
34+
rule.getOnLimitExceedPolicy(),
35+
bandDocs,
36+
rule.getRuleSetIdOrNull()
37+
);
38+
}
39+
40+
public static RateLimitRule toDomain(RateLimitRuleDocument doc) {
41+
RateLimitRule.Builder builder = RateLimitRule.builder(doc.getId())
42+
.name(doc.getName())
43+
.enabled(doc.isEnabled())
44+
.scope(doc.getScope())
45+
.keyStrategyId(doc.getKeyStrategyId())
46+
.onLimitExceedPolicy(doc.getOnLimitExceedPolicy())
47+
.ruleSetId(doc.getRuleSetId());
48+
49+
for (RateLimitBandDocument bandDoc : doc.getBands()) {
50+
builder.addBand(toDomain(bandDoc));
51+
}
52+
return builder.build();
53+
}
54+
55+
public static RateLimitBandDocument toDto(RateLimitBand band) {
56+
// Assumes current Band design with windowSeconds + capacity + label
57+
long windowSeconds = band.getWindow().getSeconds();
58+
long capacity = band.getCapacity();
59+
String label = band.getLabel();
60+
61+
return new RateLimitBandDocument(windowSeconds, capacity, label);
62+
}
63+
64+
public static RateLimitBand toDomain(RateLimitBandDocument doc) {
65+
return RateLimitBand.builder(Duration.ofSeconds(doc.getWindowSeconds()), doc.getCapacity())
66+
.label(doc.getLabel())
67+
.build();
68+
}
69+
70+
/* ========= DTO ↔ Bson(Document) ========= */
71+
72+
public static Document toBson(RateLimitRuleDocument doc) {
73+
List<Document> bandDocs = new ArrayList<>();
74+
for (RateLimitBandDocument band : doc.getBands()) {
75+
bandDocs.add(toBson(band));
76+
}
77+
78+
return new Document()
79+
.append("id", doc.getId())
80+
.append("name", doc.getName())
81+
.append("enabled", doc.isEnabled())
82+
.append("scope", doc.getScope().name())
83+
.append("keyStrategyId", doc.getKeyStrategyId())
84+
.append("onLimitExceedPolicy", doc.getOnLimitExceedPolicy().name())
85+
.append("ruleSetId", doc.getRuleSetId())
86+
.append("bands", bandDocs);
87+
}
88+
89+
public static RateLimitRuleDocument fromBson(Document doc) {
90+
String id = doc.getString("id");
91+
String name = doc.getString("name");
92+
boolean enabled = doc.getBoolean("enabled", true);
93+
LimitScope scope = LimitScope.valueOf(doc.getString("scope"));
94+
String keyStrategyId = doc.getString("keyStrategyId");
95+
OnLimitExceedPolicy policy = OnLimitExceedPolicy.valueOf(doc.getString("onLimitExceedPolicy"));
96+
String ruleSetId = doc.getString("ruleSetId");
97+
98+
@SuppressWarnings("unchecked")
99+
List<Document> bandDocs = (List<Document>) doc.get("bands");
100+
List<RateLimitBandDocument> bands = new ArrayList<>();
101+
if (bandDocs != null) {
102+
for (Document bd : bandDocs) {
103+
bands.add(fromBsonBand(bd));
104+
}
105+
}
106+
107+
return new RateLimitRuleDocument(
108+
id,
109+
name,
110+
enabled,
111+
scope,
112+
keyStrategyId,
113+
policy,
114+
bands,
115+
ruleSetId
116+
);
117+
}
118+
119+
private static Document toBson(RateLimitBandDocument doc) {
120+
return new Document()
121+
.append("windowSeconds", doc.getWindowSeconds())
122+
.append("capacity", doc.getCapacity())
123+
.append("label", doc.getLabel());
124+
}
125+
126+
private static RateLimitBandDocument fromBsonBand(Document d) {
127+
long windowSeconds = d.getLong("windowSeconds");
128+
long capacity = d.getLong("capacity");
129+
String label = d.getString("label");
130+
131+
return new RateLimitBandDocument(windowSeconds, capacity, label);
132+
}
133+
}

0 commit comments

Comments
 (0)