Skip to content

Commit d949810

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feature/senocak/add-redis-command-listener
2 parents 7b7dcf0 + 669f917 commit d949810

File tree

66 files changed

+13137
-24
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+13137
-24
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ Redis OM Spring provides all Spring Data Redis capabilities, plus:
7979
using `@EnableRedisDocumentRepositories`
8080
- Declarative search indexes via `@Indexed`
8181
- Full-text search indexes via `@Searchable`
82+
- **Dynamic index naming and multi-tenant support** with SpEL expressions and `RedisIndexContext`
83+
- **Index migration service** with Blue-Green, Dual-Write, and In-Place migration strategies
8284
- `EntityStream`s: Streams-based Query and Aggregations Builder
8385
- `@Bloom` annotation to determine very fast, with and with high degree of certainty, whether a value is in a
8486
collection.
@@ -265,6 +267,19 @@ public class Company {
265267
}
266268
```
267269

270+
For **multi-tenant applications**, you can use dynamic index naming with SpEL expressions:
271+
272+
```java
273+
// Dynamic index naming for multi-tenant support
274+
@Document
275+
@IndexingOptions(indexName = "#{@environment.getProperty('app.tenant')}_companies_idx")
276+
public class Company {
277+
@Id private String id;
278+
@Searchable private String name;
279+
// other fields...
280+
}
281+
```
282+
268283
Redis OM Spring, replaces the conventional `UUID` primary key strategy generation with a `ULID` (Universally Unique
269284
Lexicographically Sortable Identifier) which is faster to generate and easier on the eyes.
270285

demos/roms-multitenant/README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Redis OM Spring Multi-Tenant Demo
2+
3+
This demo showcases the **Dynamic Indexing** features of Redis OM Spring, enabling multi-tenant SaaS applications with complete data isolation.
4+
5+
## Features Demonstrated
6+
7+
### 1. SpEL-based Dynamic Index Naming
8+
9+
Use Spring Expression Language (SpEL) in `@IndexingOptions` annotations:
10+
11+
```java
12+
@Document
13+
@IndexingOptions(
14+
indexName = "products_#{@tenantService.getCurrentTenant()}_idx",
15+
keyPrefix = "#{@tenantService.getCurrentTenant()}:products:"
16+
)
17+
public class Product { ... }
18+
```
19+
20+
### 2. Multi-Tenant Index Isolation
21+
22+
Each tenant gets their own:
23+
- **Search Index**: `products_acme_idx`, `products_globex_idx`
24+
- **Key Prefix**: `acme:products:*`, `globex:products:*`
25+
- **Complete Data Isolation**: Searches only return tenant's own data
26+
27+
### 3. Environment-based Configuration
28+
29+
Configure indexes based on deployment environment:
30+
31+
```java
32+
@IndexingOptions(
33+
indexName = "orders_#{@environment.getProperty('app.environment')}_idx"
34+
)
35+
```
36+
37+
### 4. Ephemeral Indexes with TTL
38+
39+
Create temporary indexes that auto-delete:
40+
41+
```java
42+
ephemeralIndexService.createEphemeralIndex(
43+
Product.class,
44+
"temp_analytics_idx",
45+
Duration.ofMinutes(30)
46+
);
47+
```
48+
49+
### 5. Index Migration & Aliasing
50+
51+
Zero-downtime schema migrations using Redis aliases:
52+
53+
```java
54+
// Blue-Green deployment
55+
indexer.createAlias("products_v2_idx", "products");
56+
indexer.updateAlias("products_v1_idx", "products_v2_idx", "products");
57+
```
58+
59+
### 6. Bulk Index Operations
60+
61+
Manage all indexes at once:
62+
63+
```java
64+
indexer.createIndexes(); // Create all registered indexes
65+
indexer.dropIndexes(); // Drop all managed indexes
66+
indexer.listIndexes(); // List all index names
67+
```
68+
69+
## Running the Demo
70+
71+
### Prerequisites
72+
73+
1. Redis Stack running on localhost:6379
74+
```bash
75+
docker run -p 6379:6379 redis/redis-stack
76+
```
77+
78+
2. Build the project:
79+
```bash
80+
./gradlew build -x test
81+
```
82+
83+
### Start the Application
84+
85+
```bash
86+
./gradlew :demos:roms-multitenant:bootRun
87+
```
88+
89+
Watch the console for a visual walkthrough of all features!
90+
91+
## REST API
92+
93+
### Product Operations (Tenant-Scoped)
94+
95+
```bash
96+
# Create a product for tenant "acme"
97+
curl -X POST http://localhost:8080/api/tenants/acme/products \
98+
-H "Content-Type: application/json" \
99+
-d '{
100+
"name": "Widget Pro",
101+
"description": "Professional grade widget",
102+
"category": "Electronics",
103+
"sku": "WDG-PRO-001",
104+
"price": 299.99,
105+
"stockQuantity": 50,
106+
"active": true
107+
}'
108+
109+
# List products for tenant "acme"
110+
curl http://localhost:8080/api/tenants/acme/products
111+
112+
# Search products
113+
curl "http://localhost:8080/api/tenants/acme/products/search?q=widget"
114+
115+
# Find by category
116+
curl http://localhost:8080/api/tenants/acme/products/category/Electronics
117+
118+
# Find by price range
119+
curl "http://localhost:8080/api/tenants/acme/products/price-range?minPrice=100&maxPrice=500"
120+
```
121+
122+
### Admin Operations
123+
124+
```bash
125+
# List all managed indexes
126+
curl http://localhost:8080/api/admin/indexes
127+
128+
# Create all registered indexes
129+
curl -X POST http://localhost:8080/api/admin/indexes/create-all
130+
131+
# Create index for specific tenant
132+
curl -X POST http://localhost:8080/api/admin/tenants/acme/indexes
133+
134+
# Check if tenant index exists
135+
curl http://localhost:8080/api/admin/tenants/acme/indexes/exists
136+
137+
# Create ephemeral index (60 second TTL)
138+
curl -X POST http://localhost:8080/api/admin/indexes/ephemeral \
139+
-H "Content-Type: application/json" \
140+
-d '{"indexName": "temp_batch_idx", "ttlSeconds": 60}'
141+
142+
# Create index alias
143+
curl -X POST http://localhost:8080/api/admin/indexes/products_acme_idx/alias/products
144+
145+
# Remove index alias
146+
curl -X DELETE http://localhost:8080/api/admin/indexes/products_acme_idx/alias/products
147+
```
148+
149+
## Multi-Tenant Usage Pattern
150+
151+
```java
152+
@Service
153+
public class ProductService {
154+
155+
@Autowired
156+
private TenantService tenantService;
157+
158+
@Autowired
159+
private ProductRepository productRepository;
160+
161+
public List<Product> getProductsForTenant(String tenantId) {
162+
tenantService.setCurrentTenant(tenantId);
163+
try {
164+
return productRepository.findAll();
165+
} finally {
166+
tenantService.clearTenant();
167+
}
168+
}
169+
}
170+
```
171+
172+
## Configuration
173+
174+
### application.yaml
175+
176+
```yaml
177+
spring:
178+
data:
179+
redis:
180+
host: localhost
181+
port: 6379
182+
183+
app:
184+
environment: development # Used in environment-based index names
185+
version: 1.0.0
186+
```
187+
188+
## Key Classes
189+
190+
| Class | Description |
191+
|-------|-------------|
192+
| `TenantService` | ThreadLocal tenant context management |
193+
| `Product` | Entity with dynamic index annotations |
194+
| `ProductController` | Tenant-scoped REST operations |
195+
| `AdminController` | Index management endpoints |
196+
| `DemoRunner` | Visual feature walkthrough |
197+
198+
## Learn More
199+
200+
- [Dynamic Indexing Design Document](https://github.com/redis/redis-om-spring/wiki/Dynamic-Indexing-Feature-Design)
201+
- [Redis OM Spring Documentation](https://github.com/redis/redis-om-spring)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
plugins {
2+
id 'java'
3+
id 'org.springframework.boot'
4+
id 'io.spring.dependency-management'
5+
}
6+
7+
java {
8+
toolchain {
9+
languageVersion = JavaLanguageVersion.of(21)
10+
}
11+
}
12+
13+
// Don't publish this module
14+
tasks.matching { it.name.startsWith('publish') }.configureEach {
15+
enabled = false
16+
}
17+
18+
repositories {
19+
mavenLocal()
20+
mavenCentral()
21+
maven {
22+
name = 'Spring Milestones'
23+
url = 'https://repo.spring.io/milestone'
24+
}
25+
maven {
26+
name = 'Spring Snapshots'
27+
url = 'https://repo.spring.io/snapshot'
28+
}
29+
}
30+
31+
dependencies {
32+
implementation project(':redis-om-spring')
33+
34+
// Important for RedisOM annotation processing!
35+
annotationProcessor project(':redis-om-spring')
36+
testAnnotationProcessor project(':redis-om-spring')
37+
38+
// Lombok
39+
compileOnly 'org.projectlombok:lombok'
40+
annotationProcessor 'org.projectlombok:lombok'
41+
testCompileOnly 'org.projectlombok:lombok'
42+
testAnnotationProcessor 'org.projectlombok:lombok'
43+
44+
// Spring Boot starters
45+
implementation 'org.springframework.boot:spring-boot-starter-web'
46+
implementation 'org.springframework.boot:spring-boot-devtools'
47+
48+
// Test dependencies
49+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
50+
testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}"
51+
testImplementation "org.testcontainers:junit-jupiter"
52+
}
53+
54+
// Use -parameters flag for Spring
55+
tasks.withType(JavaCompile).configureEach {
56+
options.compilerArgs << '-parameters'
57+
}
58+
59+
test {
60+
useJUnitPlatform()
61+
}

0 commit comments

Comments
 (0)