Skip to content

Commit d895b41

Browse files
Andrés Contreras GuillénAndrés Contreras Guillén
authored andcommitted
docs: correct README to match real CacheAdapter SPI, properties, and schema
1 parent d949291 commit d895b41

1 file changed

Lines changed: 147 additions & 78 deletions

File tree

README.md

Lines changed: 147 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# Firefly Framework - Cache PostgreSQL Adapter
1+
# Firefly Framework - Cache - Postgres
22

33
[![CI](https://github.com/fireflyframework/fireflyframework-cache-postgres/actions/workflows/ci.yml/badge.svg)](https://github.com/fireflyframework/fireflyframework-cache-postgres/actions/workflows/ci.yml)
44
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
55
[![Java](https://img.shields.io/badge/Java-21%2B-orange.svg)](https://openjdk.org)
66
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.x-green.svg)](https://spring.io/projects/spring-boot)
77

8-
> Reactive, durable PostgreSQL/R2DBC cache provider adapter for the Firefly Framework cache abstraction — a SQL-backed, non-blocking `FireflyCache` implementation with TTL expiration and background cleanup.
8+
> Durable PostgreSQL/R2DBC cache provider adapter for the Firefly Framework cache abstraction — a fully reactive, SQL-backed `CacheAdapter` with TTL expiry, atomic put-if-absent, and prefix eviction.
99
1010
---
1111

@@ -25,58 +25,64 @@
2525
## Overview
2626

2727
`fireflyframework-cache-postgres` is one of the pluggable cache **provider adapters** for the Firefly
28-
Framework cache abstraction. It implements the cache SPI defined in
29-
[`fireflyframework-cache-core`](https://github.com/fireflyframework/fireflyframework-cache-core)
30-
(`FireflyCache`, `CacheProvider`, `CacheType`) on top of **PostgreSQL** using
31-
**R2DBC** for fully reactive, non-blocking database access.
28+
Framework cache abstraction. It implements the `CacheAdapter` port defined in
29+
[`fireflyframework-cache`](https://github.com/fireflyframework/fireflyframework-cache) (the cache core)
30+
using **PostgreSQL** as a persistent, distributed cache backend over fully reactive **R2DBC**.
3231

3332
The cache core ships **Caffeine** as the in-process default. Every other backend — Redis, Hazelcast,
34-
JCache and PostgreSQL — lives in its own adapter module so applications only pull in the dependency
35-
they actually use. This module is the PostgreSQL adapter: dropping it on the classpath and selecting
36-
`firefly.cache.provider=postgres` routes the unified cache API to a relational table instead of an
37-
in-memory store.
33+
JCache and PostgreSQL — lives in its own adapter module, so an application only pulls in the
34+
dependency it actually uses. This module contributes a `PostgresProvider` (discovered through the
35+
`CacheProviderFactory` `ServiceLoader` SPI) and a `PostgresCacheAutoConfiguration`. Once on the
36+
classpath with `firefly.cache.postgres.enabled=true`, the cache core makes the `POSTGRES` cache type
37+
available to `FireflyCacheManager` and selects it when `firefly.cache.default-cache-type=POSTGRES`
38+
(or via `AUTO`).
3839

3940
Use this adapter when you want a **durable, SQL-backed cache** rather than an in-memory or Redis
40-
cache — for example, when entries must survive process restarts, when you want cache contents to be
41-
queryable/auditable through ordinary SQL, or when you would rather reuse your existing PostgreSQL
42-
infrastructure than operate a separate cache cluster. Because it is built on R2DBC and Project
43-
Reactor, it integrates cleanly with reactive Spring WebFlux stacks and the rest of the Firefly
44-
Framework without introducing blocking calls.
41+
cache — for example, when entries must survive process restarts, when cache contents should be
42+
queryable and auditable through ordinary SQL, or when you would rather reuse your existing PostgreSQL
43+
infrastructure than operate a separate cache cluster. Cache entries live in a single relational table
44+
keyed by `(cache_name, cache_key)`, with values serialized to JSON and stored in a `BYTEA` column;
45+
time-to-live is enforced through an `expires_at` timestamp checked lazily on every read. Because the
46+
adapter is built on R2DBC and Project Reactor, every operation is non-blocking and integrates cleanly
47+
with Spring WebFlux and the rest of the Firefly Framework.
4548

4649
### Where it sits in the framework
4750

4851
| Module | Role |
4952
| --- | --- |
50-
| `fireflyframework-cache-core` | Cache SPI + Caffeine default (`FireflyCache`, `CacheProvider`, `CacheManager`, `CacheType`) |
53+
| `fireflyframework-cache` | Cache abstraction + SPI + Caffeine default (`CacheAdapter`, `CacheType`, `CacheProviderFactory`, `FireflyCacheManager`) |
5154
| `fireflyframework-cache-redis` | Redis provider adapter |
5255
| `fireflyframework-cache-hazelcast` | Hazelcast provider adapter |
5356
| `fireflyframework-cache-jcache` | JCache (JSR-107) provider adapter |
5457
| **`fireflyframework-cache-postgres`** | **PostgreSQL / R2DBC provider adapter (this module)** |
5558

5659
## Features
5760

58-
- **Reactive end to end** — implements `FireflyCache` over R2DBC; no blocking JDBC calls, suitable for Spring WebFlux and reactive Firefly services.
59-
- **Durable, SQL-backed storage** — cache entries persist in a relational table (`firefly_cache` by default) so they survive restarts and remain queryable with plain SQL.
60-
- **Reports as `CacheType.POSTGRES`** — slots into the cache abstraction alongside Caffeine, Redis, Hazelcast and JCache via the same `CacheProvider` SPI.
61-
- **TTL-based expiration** — per-entry time-to-live with a configurable default; zero/negative means no expiration.
62-
- **Background cleanup** — a periodic purge job removes expired rows on a configurable interval, keeping the table lean.
63-
- **Connection pooling** — uses `r2dbc-pool` for efficient, bounded reactive connection management.
64-
- **Jackson serialization** — values are serialized via `jackson-databind` for compact, portable storage.
65-
- **Spring Boot auto-configuration**`PostgresCacheAutoConfiguration` wires everything automatically; gated by `@ConditionalOnClass(CacheProvider.class)` and the `firefly.cache.postgres.enabled` property.
66-
- **Type-safe configuration** — all settings exposed through `PostgresCacheProperties` under the `firefly.cache.postgres` prefix.
61+
- Full `CacheAdapter` implementation (`PostgresCacheAdapter`) backed by PostgreSQL R2DBC.
62+
- Reactive, non-blocking operations with per-operation connection lifecycle via `Mono.usingWhen`.
63+
- TTL support with lazy expiry on read (`expires_at IS NULL OR expires_at > NOW()`) and write-time `expires_at = NOW() + ttl`.
64+
- JSON serialization to a `BYTEA` column through the framework `CacheSerializer` (`JsonCacheSerializer`).
65+
- Atomic `putIfAbsent` using `INSERT ... ON CONFLICT DO NOTHING`; upserts via `ON CONFLICT DO UPDATE`.
66+
- Native prefix eviction (`evictByPrefix`) via `DELETE ... WHERE cache_key LIKE prefix || '%'`.
67+
- Per-cache key namespacing (`<keyPrefix>:<cacheName>:<key>`) so one table hosts many named caches.
68+
- Pooled connections via `r2dbc-pool` with configurable min/max pool sizing.
69+
- Optional automatic schema creation (cache table + partial expiry index) on startup.
70+
- Connection health checks via `Connection.validate(ValidationDepth.REMOTE)`, surfaced as `CacheHealth`.
71+
- In-process statistics (requests, hits, misses, puts, evictions, entry count) as `CacheStats`.
72+
- `ServiceLoader` SPI registration (`PostgresProvider`, `CacheType.POSTGRES`, priority 15) plus Spring Boot auto-configuration.
6773

6874
## Requirements
6975

7076
- Java 21+ (Java 25 recommended)
7177
- Spring Boot 3.x
7278
- Maven 3.9+
73-
- A reachable PostgreSQL database (any supported version) with R2DBC connectivity
74-
- `fireflyframework-cache-core` on the classpath (pulled in transitively as a dependency)
79+
- PostgreSQL 12+ reachable over R2DBC
80+
- `fireflyframework-cache` on the classpath (pulled in transitively as a dependency)
7581

7682
## Installation
7783

78-
Add the adapter to your application. The version is managed by the Firefly Framework
79-
parent POM / BOM, so you normally omit `<version>`:
84+
Add the adapter to your application. The version is managed by the Firefly Framework parent POM / BOM,
85+
so you normally omit `<version>`:
8086

8187
```xml
8288
<dependency>
@@ -85,8 +91,8 @@ parent POM / BOM, so you normally omit `<version>`:
8591
</dependency>
8692
```
8793

88-
This brings in `fireflyframework-cache-core` (the cache SPI), the `r2dbc-postgresql` driver,
89-
`r2dbc-pool` and `jackson-databind` transitively.
94+
This brings in `fireflyframework-cache` (the cache abstraction), the `r2dbc-postgresql` driver,
95+
`r2dbc-pool` and the R2DBC SPI transitively.
9096

9197
If you are not inheriting the Firefly parent, import the BOM in your `dependencyManagement` and let it
9298
manage the version:
@@ -107,99 +113,162 @@ manage the version:
107113

108114
## Quick Start
109115

110-
The adapter is auto-configured. To activate the PostgreSQL backend:
116+
1. **Add the dependencies**the cache core and this adapter:
111117

112-
1. **Add the dependency** (see [Installation](#installation)).
113-
2. **Select the provider** and point it at your database in `application.yml`:
118+
```xml
119+
<dependencies>
120+
<dependency>
121+
<groupId>org.fireflyframework</groupId>
122+
<artifactId>fireflyframework-cache</artifactId>
123+
</dependency>
124+
<dependency>
125+
<groupId>org.fireflyframework</groupId>
126+
<artifactId>fireflyframework-cache-postgres</artifactId>
127+
</dependency>
128+
</dependencies>
129+
```
130+
131+
2. **Enable the provider and point it at your database** in `application.yml`:
114132

115133
```yaml
116134
firefly:
117135
cache:
118-
provider: postgres # route the unified cache abstraction to this adapter
136+
default-cache-type: POSTGRES # select this adapter (or AUTO to let the core pick)
119137
postgres:
120-
url: r2dbc:postgresql://localhost:5432/mydb
121-
username: firefly
122-
password: ${DB_PASSWORD}
123-
table-name: firefly_cache
124-
default-ttl: 10m
125-
cleanup-interval: 5m
138+
enabled: true
139+
host: localhost
140+
port: 5432
141+
database: appdb
142+
username: app
143+
password: secret
126144
```
127145
128-
3. **Use the unified cache API** — inject the `FireflyCache` (or higher-level cache facade) provided by
129-
`fireflyframework-cache-core`; it is now backed by PostgreSQL with no other code changes:
146+
3. **Use the unified cache API** — obtain a `CacheAdapter` from the core `FireflyCacheManager`; it is
147+
now backed by PostgreSQL with no other code changes:
130148

131149
```java
132-
import org.fireflyframework.cache.FireflyCache;
150+
import org.fireflyframework.cache.core.CacheAdapter;
151+
import org.fireflyframework.cache.manager.FireflyCacheManager;
133152
import org.springframework.stereotype.Service;
134153
import reactor.core.publisher.Mono;
135154
136155
@Service
137156
public class ProductService {
138157
139-
private final FireflyCache cache;
158+
private final CacheAdapter cache;
140159
141-
public ProductService(FireflyCache cache) {
142-
this.cache = cache; // CacheType.POSTGRES when this adapter is selected
160+
public ProductService(FireflyCacheManager cacheManager) {
161+
this.cache = cacheManager.getCache("products"); // CacheType.POSTGRES when selected
143162
}
144163
145164
public Mono<Product> findById(String id) {
146-
return cache.get(id, Product.class)
147-
.switchIfEmpty(loadFromDatabase(id)
148-
.flatMap(product -> cache.put(id, product).thenReturn(product)));
165+
return cache.<String, Product>get(id, Product.class)
166+
.flatMap(opt -> opt.map(Mono::just).orElseGet(() ->
167+
loadFromDatabase(id).flatMap(p -> cache.put(id, p).thenReturn(p))));
149168
}
150169
}
151170
```
152171

153-
Because every adapter implements the same `FireflyCache` SPI, switching between Caffeine, Redis,
154-
Hazelcast, JCache and PostgreSQL is a configuration change (`firefly.cache.provider`) plus the matching
155-
dependency — application code stays the same.
172+
Because every adapter implements the same `CacheAdapter` SPI, switching between Caffeine, Redis,
173+
Hazelcast, JCache and PostgreSQL is a configuration change (`firefly.cache.default-cache-type`) plus
174+
the matching dependency — application code stays the same.
156175

157176
## Configuration
158177

159-
All adapter-specific properties live under the `firefly.cache.postgres` prefix and are bound by
160-
`PostgresCacheProperties`. The provider is selected by the core-owned `firefly.cache.provider` key.
178+
All adapter-specific properties live under the `firefly.cache.postgres` prefix and are bound by the
179+
core `CacheProperties.PostgresConfig`. The active provider is chosen by the core-owned
180+
`firefly.cache.default-cache-type` key.
161181

162182
```yaml
163183
firefly:
164184
cache:
165-
provider: postgres # core: choose the active provider (default: caffeine)
185+
default-cache-type: POSTGRES # core: choose the active provider (default: CAFFEINE)
166186
postgres:
167-
enabled: true # default: true
168-
url: # R2DBC URL, e.g. r2dbc:postgresql://localhost:5432/mydb
169-
username: # database username
170-
password: # database password
171-
table-name: firefly_cache # default: firefly_cache
172-
default-ttl: 0s # default: 0 (no expiration)
173-
cleanup-interval: 5m # default: 5 minutes
187+
enabled: true # default: false
188+
host: localhost # default: localhost
189+
port: 5432 # default: 5432
190+
database: appdb # default: none
191+
username: app # default: none
192+
password: secret # default: none
193+
schema: public # default: public
194+
cache-table: firefly_cache_entries # default: firefly_cache_entries
195+
cache-name: default # default: default
196+
key-prefix: "" # default: empty
197+
default-ttl: PT30M # default: 30 minutes
198+
auto-create-schema: true # default: true
199+
max-pool-size: 10 # default: 10
200+
min-pool-size: 1 # default: 1
201+
properties: {} # extra R2DBC driver options
174202
```
175203

176204
| Property | Type | Default | Description |
177205
| --- | --- | --- | --- |
178-
| `firefly.cache.provider` | `CacheType` | `caffeine` | Core property that selects the active provider. Set to `postgres` to use this adapter. |
179-
| `firefly.cache.postgres.enabled` | `boolean` | `true` | Enables/disables the PostgreSQL cache auto-configuration. |
180-
| `firefly.cache.postgres.url` | `String` | — | R2DBC connection URL (e.g. `r2dbc:postgresql://host:5432/db`). |
206+
| `firefly.cache.default-cache-type` | `CacheType` | `CAFFEINE` | Core property selecting the active provider. Set to `POSTGRES` (or `AUTO`) to use this adapter. |
207+
| `firefly.cache.postgres.enabled` | `boolean` | `false` | Enables the PostgreSQL cache provider and its auto-configuration. |
208+
| `firefly.cache.postgres.host` | `String` | `localhost` | PostgreSQL host. |
209+
| `firefly.cache.postgres.port` | `int` | `5432` | PostgreSQL port. |
210+
| `firefly.cache.postgres.database` | `String` | — | Database name. |
181211
| `firefly.cache.postgres.username` | `String` | — | Database username. |
182212
| `firefly.cache.postgres.password` | `String` | — | Database password. |
183-
| `firefly.cache.postgres.table-name` | `String` | `firefly_cache` | Table that stores cache entries. |
184-
| `firefly.cache.postgres.default-ttl` | `Duration` | `0s` (none) | Default time-to-live for entries; zero or negative means no expiration. |
185-
| `firefly.cache.postgres.cleanup-interval` | `Duration` | `5m` | How often the background job purges expired entries. |
213+
| `firefly.cache.postgres.schema` | `String` | `public` | Schema that holds the cache table. |
214+
| `firefly.cache.postgres.cache-table` | `String` | `firefly_cache_entries` | Cache table name. |
215+
| `firefly.cache.postgres.cache-name` | `String` | `default` | Default logical cache name. |
216+
| `firefly.cache.postgres.key-prefix` | `String` | `""` | Prefix applied to namespaced keys. |
217+
| `firefly.cache.postgres.default-ttl` | `Duration` | `PT30M` | Default entry TTL; zero/negative means no expiration. |
218+
| `firefly.cache.postgres.auto-create-schema` | `boolean` | `true` | Create the cache table and expiry index on startup. |
219+
| `firefly.cache.postgres.max-pool-size` | `int` | `10` | Maximum R2DBC pool connections. |
220+
| `firefly.cache.postgres.min-pool-size` | `int` | `1` | Initial/minimum R2DBC pool connections. |
221+
| `firefly.cache.postgres.properties` | `Map` | `{}` | Extra options passed to the PostgreSQL R2DBC driver. |
222+
223+
> Connection settings come **only** from `firefly.cache.postgres.*`, never from `spring.r2dbc.*` —
224+
> the cache uses a dedicated, pooled `ConnectionFactory` bean
225+
> (`fireflyCachePostgresConnectionFactory`) independent of your application datasource.
226+
227+
### Storage schema
228+
229+
When `auto-create-schema` is `true`, the adapter provisions (idempotently) the following table and a
230+
partial index supporting expiry scans:
231+
232+
```sql
233+
CREATE TABLE IF NOT EXISTS "public"."firefly_cache_entries" (
234+
cache_name VARCHAR(255) NOT NULL,
235+
cache_key VARCHAR(1024) NOT NULL,
236+
value BYTEA NOT NULL,
237+
expires_at TIMESTAMPTZ,
238+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
239+
PRIMARY KEY (cache_name, cache_key)
240+
);
241+
CREATE INDEX IF NOT EXISTS firefly_cache_entries_expires_idx
242+
ON "public"."firefly_cache_entries" (expires_at) WHERE expires_at IS NOT NULL;
243+
```
244+
245+
If you disable `auto-create-schema`, create this table and index manually before first use.
186246

187247
## How It Works
188248

189-
- **`PostgresCacheAutoConfiguration`** — Spring Boot `@AutoConfiguration` registered in
249+
- **`PostgresCacheAutoConfiguration`** (`org.fireflyframework.cache.config`) — Spring Boot
250+
`@AutoConfiguration` registered in
190251
`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`. Activates when
191-
`CacheProvider` is on the classpath and `firefly.cache.postgres.enabled` is `true` (the default),
192-
and enables `PostgresCacheProperties`.
193-
- **`PostgresCacheConfiguration`** — wires the resolved `PostgresCacheProperties` into the provider.
194-
- **`PostgresFireflyCache`** — the `FireflyCache` implementation. Stores entries in a relational table
195-
via an R2DBC `ConnectionFactory`, serializes values with Jackson, reports `CacheType.POSTGRES`, and
196-
supports TTL-based expiration with periodic cleanup of stale rows.
197-
- **`PostgresCacheManager`** — manages the lifecycle of PostgreSQL-backed cache instances.
252+
the R2DBC SPI is on the classpath and `firefly.cache.postgres.enabled=true`. Builds the pooled
253+
`fireflyCachePostgresConnectionFactory` bean and, when enabled, provisions the schema.
254+
- **`PostgresProvider`** (`org.fireflyframework.cache.spi.providers`) — the `CacheProviderFactory` SPI
255+
implementation, registered for `ServiceLoader` in
256+
`META-INF/services/org.fireflyframework.cache.spi.CacheProviderFactory`. Reports `CacheType.POSTGRES`
257+
with priority 15 and is available when the connection factory is present and the provider is enabled.
258+
- **`PostgresCacheHelper`** (`org.fireflyframework.cache.factory`) — the factory helper the cache core
259+
references reflectively to build adapters; its fully-qualified name is load-bearing and must not be
260+
renamed or moved.
261+
- **`PostgresCacheAdapter`** (`org.fireflyframework.cache.adapter.postgres`) — the `CacheAdapter`
262+
implementation. Stores entries in the relational table via the R2DBC `ConnectionFactory`, serializes
263+
values with `JsonCacheSerializer`, namespaces keys per cache, enforces TTL, and reports
264+
`CacheStats`/`CacheHealth`.
265+
- **`PostgresCacheConfig`** (`org.fireflyframework.cache.adapter.postgres`) — immutable per-adapter
266+
config (cache name, key prefix, default TTL, schema, table).
198267

199268
## Documentation
200269

201270
- Firefly Framework documentation hub and module catalog: <https://github.com/fireflyframework>
202-
- Cache abstraction SPI: [`fireflyframework-cache-core`](https://github.com/fireflyframework/fireflyframework-cache-core)
271+
- Cache abstraction & SPI: [`fireflyframework-cache`](https://github.com/fireflyframework/fireflyframework-cache)
203272
- Sibling adapters:
204273
[Redis](https://github.com/fireflyframework/fireflyframework-cache-redis) ·
205274
[Hazelcast](https://github.com/fireflyframework/fireflyframework-cache-hazelcast) ·

0 commit comments

Comments
 (0)