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
23 changes: 14 additions & 9 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:url-repo: https://github.com/MarcGiffing/bucket4j-spring-boot-starter
:url: https://github.com/MarcGiffing/bucket4j-spring-boot-starter/tree/master
:url-examples: {url}/examples
:url-config-cache: {url}/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache
:url-config-cache: {url}/cache/

image:{url-repo}/actions/workflows/maven.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/maven.yml]
image:{url-repo}/actions/workflows/codeql.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/codeql.yml]
Expand Down Expand Up @@ -684,35 +684,40 @@ The following list contains the Caching implementation which will be autoconfigu
|*cache-to-use*

|N
|{url-config-cache}/jcache/JCacheBucket4jConfiguration.java[JSR 107 -JCache]
|{url-config-cache}/cache-jcache[JSR 107 -JCache]
|jcache

|Yes
|{url-config-cache}/ignite/IgniteBucket4jCacheConfiguration.java[Ignite]
|{url-config-cache}/cache-ignite[Ignite]
|jcache-ignite

|N
|{url-config-cache}/cache-postgresql[PostgreSQL]
|jcache


|no
|{url-config-cache}/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java[Hazelcast]
|{url-config-cache}/cache-hazelcast[Hazelcast]
|hazelcast-spring

|yes
|{url-config-cache}/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java[Hazelcast]
|{url-config-cache}/cache-hazelcast[Hazelcast]
|hazelcast-reactive

|Yes
|{url-config-cache}/infinispan/InfinispanBucket4jCacheConfiguration.java[Infinispan]
|{url-config-cache}/cache-infinispan[Infinispan]
|infinispan

|No
|{url-config-cache}/redis/jedis/JedisBucket4jConfiguration.java[Redis-Jedis]
|{url-config-cache}/cache-redis-jedis[Redis-Jedis]
|redis-jedis

|Yes
|{url-config-cache}/redis/lettuce/LettuceBucket4jConfiguration.java[Redis-Lettuce]
|{url-config-cache}/cache-lettuce[Redis-Lettuce]
|redis-lettuce

|Yes
|{url-config-cache}/redis/redisson/RedissonBucket4jConfiguration.java[Redis-Redisson]
|{url-config-cache}/cache-redis-resdisson[Redis-Redisson]
|redis-redisson

|===
Expand Down
9 changes: 9 additions & 0 deletions cache/cache-postgresql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/target/
/.settings/
.classpath
.project
.idea/
*.iml
.factorypath
.apt_generated
.springBeans
150 changes: 150 additions & 0 deletions cache/cache-postgresql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Bucket4j PostgreSQL Cache Module

This module provides PostgreSQL database support for Bucket4j rate limiting with Spring Boot.

## Overview

The `cache-postgresql` module integrates Bucket4j with PostgreSQL, allowing you to store rate limit data in a PostgreSQL relational database. This is useful for distributed systems that already use PostgreSQL and want to leverage it for centralized rate limiting.

## Features

- **Synchronous Cache Access**: Provides synchronous rate limit token bucket operations
- **JDBC-based**: Uses Bucket4j's JDBC proxy manager for PostgreSQL connectivity
- **Spring Boot Auto-Configuration**: Automatic configuration when dependencies are present
- **Event Publishing**: Publishes cache update events to Spring's ApplicationEventPublisher
- **Configuration Caching**: Optional caching of Bucket4j configuration

## Dependencies

The module requires the following:

- `spring-boot-starter-data-jpa`: For JPA support (optional)
- `postgresql`: PostgreSQL JDBC driver
- `bucket4j_jdk17-jdbc`: Bucket4j JDBC support

## Configuration

To use the PostgreSQL cache module, add the following to your `application.properties` or `application.yml`:

```properties
bucket4j.enabled=true
bucket4j.cache-type=postgresql
```

### Database Setup

Before using the PostgreSQL cache, you need to create the necessary table for storing bucket tokens. Bucket4j uses a standard schema for JDBC storage.

Create the table with the following SQL:

```sql
CREATE TABLE IF NOT EXISTS bucket (
id VARCHAR(20) PRIMARY KEY,
state BYTEA,
expires_at BIGINT,
explicit_lock BIGINT);

CREATE INDEX IF NOT EXISTS idx_bucket4j_id ON bucket(id);
```

## Components

### PostgreSQLCacheResolver

Implements `SyncCacheResolver` and uses Bucket4j's `JdbcProxyManager` to manage rate limit buckets through JDBC connections.

**Key Features:**
- Synchronous access to rate limit tokens
- Automatic connection pooling through DataSource
- Direct integration with PostgreSQL via JDBC

### PostgreSQLCacheManager

Implements `CacheManager` for managing cache entries in the PostgreSQL database.

**Key Methods:**
- `getValue(K key)`: Retrieves cached values from the database
- `setValue(K key, V value)`: Stores or updates values in the database using PostgreSQL's `ON CONFLICT` clause

### PostgreSQLCacheListener

Listens to cache updates and publishes `CacheUpdateEvent` to the Spring ApplicationEventPublisher.

### PostgreSQLBucket4jConfiguration

Spring Boot auto-configuration class that:
- Checks if Bucket4j is enabled
- Validates DataSource availability
- Registers the `PostgreSQLCacheResolver` bean
- Optionally registers configuration cache manager
- Registers cache listener for event publishing

## Usage Example

```java
@RestController
@RequestMapping("/api")
public class MyController {

@GetMapping("/data")
@Bucket4j(bucketName = "main", capacityDescription = "10 requests per minute")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("Hello World");
}
}
```

## Configuration Properties

The following properties can be configured in `application.properties`:

```properties
# Enable Bucket4j
bucket4j.enabled=true

# Set cache type to PostgreSQL
bucket4j.cache-type=postgresql

# Optional: Cache configuration in PostgreSQL
bucket4j.filter-config-cache-enabled=true

# DataSource configuration (Spring Boot standard)
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=postgres
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
```

## Performance Considerations

1. **Connection Pooling**: Ensure proper HikariCP (or other connection pool) configuration for optimal performance
2. **Table Indexing**: The `idx_bucket4j_tokens_id` index improves lookup performance
3. **Network Latency**: PostgreSQL-based rate limiting incurs network round-trip time, making it slower than in-memory solutions
4. **Distributed Systems**: Best suited for distributed systems where a centralized database is already in use

## Advantages

- **Centralized Rate Limiting**: All instances share the same rate limit state
- **Data Persistence**: Rate limit data survives application restarts
- **Simplicity**: Leverages existing PostgreSQL infrastructure
- **Scalability**: Works well in containerized and cloud environments

## Limitations

- **Performance**: Slower than in-memory cache solutions due to database I/O
- **Synchronous Only**: This module only provides synchronous cache access
- **Database Dependency**: Requires PostgreSQL to be available and operational

## Related Modules

- `cache-jcache`: JCache (JSR-107) implementation
- `cache-redis-lettuce`: Redis Lettuce driver support
- `cache-redis-jedis`: Redis Jedis driver support
- `cache-redis-redisson`: Redis Redisson driver support
- `cache-hazelcast`: Hazelcast distributed cache support
- `cache-infinispan`: Infinispan cache support

## License

Apache License 2.0

45 changes: 45 additions & 0 deletions cache/cache-postgresql/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>parent</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>cache-postgresql</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>starter-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j_jdk17-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.giffing.bucket4j.spring.boot.starter.cache.postgresql;

import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnBucket4jEnabled;
import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnCache;
import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnSynchronousPropertyCondition;
import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties;
import com.giffing.bucket4j.spring.boot.starter.core.cache.SyncCacheResolver;
import io.github.bucket4j.postgresql.Bucket4jPostgreSQL;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;

import javax.sql.DataSource;

@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnBucket4jEnabled
@ConditionalOnSynchronousPropertyCondition
@ConditionalOnClass(Bucket4jPostgreSQL.class)
@ConditionalOnCache("postgresql")
@EnableConfigurationProperties({Bucket4JBootProperties.class})
public class PostgreSQLBucket4jConfiguration {

private final DataSource dataSource;

public PostgreSQLBucket4jConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
}

@Bean
@ConditionalOnMissingBean(SyncCacheResolver.class)
public SyncCacheResolver bucket4jCacheResolver() {
return new PostgreSQLCacheResolver(dataSource);
}

}



Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.giffing.bucket4j.spring.boot.starter.cache.postgresql;

import com.giffing.bucket4j.spring.boot.starter.core.cache.AbstractCacheResolverTemplate;
import com.giffing.bucket4j.spring.boot.starter.core.cache.CacheResolver;
import com.giffing.bucket4j.spring.boot.starter.core.cache.SyncCacheResolver;
import io.github.bucket4j.distributed.jdbc.PrimaryKeyMapper;
import io.github.bucket4j.distributed.proxy.AbstractProxyManager;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.postgresql.Bucket4jPostgreSQL;

import javax.sql.DataSource;

/**
* This class is the PostgreSQL (JDBC) implementation of the {@link CacheResolver}.
* It uses Bucket4Js {@link io.github.bucket4j.postgresql.PostgreSQLadvisoryLockBasedProxyManager} to implement the {@link ProxyManager}.
*/
public class PostgreSQLCacheResolver extends AbstractCacheResolverTemplate<String> implements SyncCacheResolver {

private final DataSource dataSource;

public PostgreSQLCacheResolver(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public String castStringToCacheKey(String key) {
return key;
}

@Override
public boolean isAsync() {
return false;
}

@Override
public AbstractProxyManager<String> getProxyManager(String cacheName) {
return Bucket4jPostgreSQL.selectForUpdateBasedBuilder(dataSource)
.primaryKeyMapper(PrimaryKeyMapper.STRING)
.build();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.giffing.bucket4j.spring.boot.starter.cache.postgresql.PostgreSQLBucket4jConfiguration
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.giffing.bucket4j.spring.boot.starter.examples.caffeine;

import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletUpdateFilterTestSuite;
import com.giffing.bucket4j.spring.boot.starter.general.tests.method.method.MethodTestSuite;
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite;
import org.junit.platform.suite.api.SelectClasses;
Expand All @@ -8,7 +9,8 @@
@Suite
@SelectClasses({
ServletTestSuite.class,
ServletUpdateFilterTestSuite.class,
MethodTestSuite.class
})
public class CaffeineGeneralSuiteTest {
public class CaffeineGeneralSuite {
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.giffing.bucket4j.spring.boot.starter.examples.ehcache;

import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite;
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletUpdateFilterTestSuite;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({
ServletTestSuite.class,
ServletUpdateFilterTestSuite.class,
})
public class EhcacheGeneralSuite {
}
5 changes: 0 additions & 5 deletions examples/general-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
<artifactId>starter-autoconfigure</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>cache-jcache</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
Expand Down
Loading
Loading