or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-caching.mdcore-cache-operations.mdindex.mdnamed-caches.md

named-caches.mddocs/

0

# Dependency Injection and Named Caches

1

2

Support for multiple cache instances using named cache injection with Play's dependency injection framework. This allows applications to use different cache configurations for different purposes (e.g., session cache, data cache, temporary cache).

3

4

## Capabilities

5

6

### @NamedCache Annotation (Java)

7

8

The @NamedCache annotation is used to inject specific named cache instances.

9

10

```java { .api }

11

/**

12

* Qualifier annotation for named cache injection

13

*/

14

@Qualifier

15

@Retention(RetentionPolicy.RUNTIME)

16

public @interface NamedCache {

17

/**

18

* The name of the cache to inject

19

* @return cache name

20

*/

21

String value();

22

}

23

```

24

25

**Usage Examples:**

26

27

```java

28

import play.cache.AsyncCacheApi;

29

import play.cache.SyncCacheApi;

30

import play.cache.NamedCache;

31

import javax.inject.Inject;

32

import java.util.concurrent.CompletionStage;

33

34

public class UserService {

35

private final AsyncCacheApi sessionCache;

36

private final AsyncCacheApi dataCache;

37

private final SyncCacheApi tempCache;

38

39

@Inject

40

public UserService(

41

@NamedCache("session") AsyncCacheApi sessionCache,

42

@NamedCache("data") AsyncCacheApi dataCache,

43

@NamedCache("temp") SyncCacheApi tempCache

44

) {

45

this.sessionCache = sessionCache;

46

this.dataCache = dataCache;

47

this.tempCache = tempCache;

48

}

49

50

// Use session cache for user sessions (short-lived)

51

public CompletionStage<Done> cacheUserSession(String sessionId, UserSession session) {

52

return sessionCache.set("session:" + sessionId, session, 3600); // 1 hour

53

}

54

55

// Use data cache for user data (longer-lived)

56

public CompletionStage<Done> cacheUserData(String userId, User user) {

57

return dataCache.set("user:" + userId, user, 86400); // 24 hours

58

}

59

60

// Use temp cache for temporary calculations (synchronous)

61

public void cacheTempResult(String key, Object result) {

62

tempCache.set("temp:" + key, result, 300); // 5 minutes

63

}

64

}

65

```

66

67

### NamedCacheImpl Class (Java)

68

69

Implementation class for the @NamedCache annotation, used internally by the DI framework.

70

71

```java { .api }

72

/**

73

* Implementation of NamedCache annotation for dependency injection

74

* See https://issues.scala-lang.org/browse/SI-8778 for why this is implemented in Java

75

*/

76

public class NamedCacheImpl implements NamedCache, Serializable {

77

/**

78

* Constructor for creating NamedCache instances

79

* @param value the cache name

80

*/

81

public NamedCacheImpl(String value);

82

83

/**

84

* Get the cache name

85

* @return the cache name

86

*/

87

public String value();

88

89

/**

90

* Hash code implementation following java.lang.Annotation specification

91

* @return hash code

92

*/

93

public int hashCode();

94

95

/**

96

* Equals implementation for annotation comparison

97

* @param o object to compare

98

* @return true if equal

99

*/

100

public boolean equals(Object o);

101

102

/**

103

* String representation of the annotation

104

* @return string representation

105

*/

106

public String toString();

107

108

/**

109

* Returns the annotation type

110

* @return NamedCache.class

111

*/

112

public Class<? extends Annotation> annotationType();

113

}

114

```

115

116

### Scala Type Alias

117

118

Scala code can reference the Java annotation through a type alias.

119

120

```scala { .api }

121

// Type alias in play.api.cache package object

122

package play.api

123

124

/**

125

* Contains the Cache access API

126

*/

127

package object cache {

128

type NamedCache = play.cache.NamedCache

129

}

130

```

131

132

**Usage Examples:**

133

134

```scala

135

import play.api.cache.{AsyncCacheApi, NamedCache}

136

import javax.inject.Inject

137

138

class ProductService @Inject()(

139

@NamedCache("products") productCache: AsyncCacheApi,

140

@NamedCache("categories") categoryCache: AsyncCacheApi,

141

defaultCache: AsyncCacheApi // Default unnamed cache

142

) {

143

144

import scala.concurrent.duration._

145

import scala.concurrent.ExecutionContext

146

147

// Use product-specific cache

148

def cacheProduct(product: Product)(implicit ec: ExecutionContext) = {

149

productCache.set(s"product:${product.id}", product, 2.hours)

150

}

151

152

// Use category-specific cache

153

def cacheCategory(category: Category)(implicit ec: ExecutionContext) = {

154

categoryCache.set(s"category:${category.id}", category, 6.hours)

155

}

156

157

// Use default cache for temporary data

158

def cacheTempData(key: String, data: Any)(implicit ec: ExecutionContext) = {

159

defaultCache.set(s"temp:$key", data, 30.minutes)

160

}

161

}

162

```

163

164

## Configuration Examples

165

166

### Application Configuration

167

168

Configure multiple named caches in `application.conf`:

169

170

```hocon

171

# Default cache configuration

172

play.cache.defaultCache = "default"

173

play.cache.bindCaches = ["session", "data", "temp"]

174

175

# EhCache configuration for multiple caches

176

play.cache.createBoundCaches = true

177

178

# Cache-specific configurations

179

ehcache {

180

caches {

181

default {

182

maxEntriesLocalHeap = 1000

183

timeToLiveSeconds = 3600

184

}

185

session {

186

maxEntriesLocalHeap = 5000

187

timeToLiveSeconds = 1800 # 30 minutes

188

}

189

data {

190

maxEntriesLocalHeap = 10000

191

timeToLiveSeconds = 86400 # 24 hours

192

}

193

temp {

194

maxEntriesLocalHeap = 500

195

timeToLiveSeconds = 300 # 5 minutes

196

}

197

}

198

}

199

```

200

201

### Guice Module Configuration

202

203

Custom Guice module for advanced cache binding:

204

205

```java

206

import com.google.inject.AbstractModule;

207

import com.google.inject.name.Names;

208

import play.cache.AsyncCacheApi;

209

import play.cache.NamedCacheImpl;

210

211

public class CacheModule extends AbstractModule {

212

@Override

213

protected void configure() {

214

// Bind named caches

215

bind(AsyncCacheApi.class)

216

.annotatedWith(new NamedCacheImpl("redis"))

217

.to(RedisAsyncCacheApi.class);

218

219

bind(AsyncCacheApi.class)

220

.annotatedWith(new NamedCacheImpl("memory"))

221

.to(MemoryAsyncCacheApi.class);

222

223

bind(AsyncCacheApi.class)

224

.annotatedWith(new NamedCacheImpl("distributed"))

225

.to(HazelcastAsyncCacheApi.class);

226

}

227

}

228

```

229

230

## Multi-Cache Patterns

231

232

### Cache Hierarchies

233

234

Use multiple caches in a hierarchy for different performance characteristics:

235

236

```java

237

public class DataService {

238

private final SyncCacheApi l1Cache; // Fast in-memory cache

239

private final AsyncCacheApi l2Cache; // Slower but larger cache

240

private final AsyncCacheApi backupCache; // Distributed/persistent cache

241

242

@Inject

243

public DataService(

244

@NamedCache("l1") SyncCacheApi l1Cache,

245

@NamedCache("l2") AsyncCacheApi l2Cache,

246

@NamedCache("backup") AsyncCacheApi backupCache

247

) {

248

this.l1Cache = l1Cache;

249

this.l2Cache = l2Cache;

250

this.backupCache = backupCache;

251

}

252

253

public CompletionStage<Optional<Data>> getData(String key) {

254

// Try L1 cache first (synchronous, fast)

255

Optional<Data> l1Result = l1Cache.getOptional(key);

256

if (l1Result.isPresent()) {

257

return CompletableFuture.completedFuture(l1Result);

258

}

259

260

// Try L2 cache (asynchronous, larger)

261

return l2Cache.getOptional(key)

262

.thenCompose(l2Result -> {

263

if (l2Result.isPresent()) {

264

// Store in L1 for next time

265

l1Cache.set(key, l2Result.get(), 300);

266

return CompletableFuture.completedFuture(l2Result);

267

}

268

269

// Try backup cache

270

return backupCache.getOptional(key)

271

.thenApply(backupResult -> {

272

if (backupResult.isPresent()) {

273

// Store in both L1 and L2

274

l1Cache.set(key, backupResult.get(), 300);

275

l2Cache.set(key, backupResult.get(), 3600);

276

}

277

return backupResult;

278

});

279

});

280

}

281

}

282

```

283

284

### Cache Specialization

285

286

Use different caches for different types of data:

287

288

```scala

289

import play.api.cache.{AsyncCacheApi, NamedCache}

290

import javax.inject.Inject

291

import scala.concurrent.duration._

292

293

class CacheService @Inject()(

294

@NamedCache("user-sessions") sessionCache: AsyncCacheApi,

295

@NamedCache("api-responses") apiCache: AsyncCacheApi,

296

@NamedCache("static-content") staticCache: AsyncCacheApi,

297

@NamedCache("analytics") analyticsCache: AsyncCacheApi

298

) {

299

300

// Short-lived session data

301

def cacheUserSession(sessionId: String, session: UserSession) = {

302

sessionCache.set(s"session:$sessionId", session, 30.minutes)

303

}

304

305

// Medium-lived API responses

306

def cacheApiResponse(endpoint: String, response: ApiResponse) = {

307

apiCache.set(s"api:$endpoint", response, 2.hours)

308

}

309

310

// Long-lived static content

311

def cacheStaticContent(path: String, content: StaticContent) = {

312

staticCache.set(s"static:$path", content, 24.hours)

313

}

314

315

// Analytics data with custom expiration

316

def cacheAnalytics(key: String, data: AnalyticsData) = {

317

analyticsCache.set(s"analytics:$key", data, Duration.Inf) // No expiration

318

}

319

}

320

```

321

322

### Cache Invalidation Coordination

323

324

Coordinate invalidation across multiple named caches:

325

326

```java

327

public class CacheManager {

328

private final AsyncCacheApi userCache;

329

private final AsyncCacheApi sessionCache;

330

private final AsyncCacheApi dataCache;

331

332

@Inject

333

public CacheManager(

334

@NamedCache("users") AsyncCacheApi userCache,

335

@NamedCache("sessions") AsyncCacheApi sessionCache,

336

@NamedCache("data") AsyncCacheApi dataCache

337

) {

338

this.userCache = userCache;

339

this.sessionCache = sessionCache;

340

this.dataCache = dataCache;

341

}

342

343

// Invalidate all data related to a user

344

public CompletionStage<Done> invalidateUser(String userId) {

345

CompletionStage<Done> userEviction = userCache.remove("user:" + userId);

346

CompletionStage<Done> sessionEviction = sessionCache.remove("session:" + userId);

347

CompletionStage<Done> dataEviction = dataCache.remove("userdata:" + userId);

348

349

return CompletableFuture.allOf(

350

userEviction.toCompletableFuture(),

351

sessionEviction.toCompletableFuture(),

352

dataEviction.toCompletableFuture()

353

).thenApply(v -> Done.getInstance());

354

}

355

356

// Clear all caches (admin operation)

357

public CompletionStage<Done> clearAllCaches() {

358

return CompletableFuture.allOf(

359

userCache.removeAll().toCompletableFuture(),

360

sessionCache.removeAll().toCompletableFuture(),

361

dataCache.removeAll().toCompletableFuture()

362

).thenApply(v -> Done.getInstance())

363

.exceptionally(throwable -> {

364

// Some caches might not support removeAll()

365

if (throwable.getCause() instanceof UnsupportedOperationException) {

366

// Log warning and continue

367

return Done.getInstance();

368

}

369

throw new RuntimeException(throwable);

370

});

371

}

372

}

373

```

374

375

## Testing with Named Caches

376

377

### Test Configuration

378

379

```java

380

public class CacheTestModule extends AbstractModule {

381

@Override

382

protected void configure() {

383

// Use in-memory caches for testing

384

bind(AsyncCacheApi.class)

385

.annotatedWith(new NamedCacheImpl("test-cache"))

386

.to(DefaultAsyncCacheApi.class);

387

}

388

}

389

```

390

391

### Test Examples

392

393

```java

394

public class UserServiceTest {

395

@Inject

396

@NamedCache("test-cache")

397

private AsyncCacheApi testCache;

398

399

@Inject

400

private UserService userService;

401

402

@Test

403

public void testCacheOperations() {

404

// Test with named cache

405

User user = new User("123", "John Doe");

406

407

CompletionStage<Done> setResult = testCache.set("user:123", user, 3600);

408

CompletionStage<Optional<User>> getResult = testCache.getOptional("user:123");

409

410

// Verify cache operations

411

assertThat(setResult.toCompletableFuture().join()).isEqualTo(Done.getInstance());

412

assertThat(getResult.toCompletableFuture().join()).contains(user);

413

}

414

}

415

```

416

417

## Best Practices

418

419

### Cache Naming Conventions

420

421

```java

422

// Use descriptive, hierarchical names

423

@NamedCache("user-sessions") // User session data

424

@NamedCache("product-catalog") // Product information

425

@NamedCache("search-results") // Search result caching

426

@NamedCache("api-rate-limits") // Rate limiting data

427

@NamedCache("temp-calculations") // Temporary computation results

428

```

429

430

### Configuration Management

431

432

```scala

433

// Use configuration-driven cache selection

434

class ConfigurableCacheService @Inject()(

435

@NamedCache("primary") primaryCache: AsyncCacheApi,

436

@NamedCache("fallback") fallbackCache: AsyncCacheApi,

437

config: Configuration

438

) {

439

440

private val useFailover = config.get[Boolean]("cache.enable-failover")

441

442

def getCache: AsyncCacheApi = {

443

if (useFailover) fallbackCache else primaryCache

444

}

445

}

446

```

447

448

### Error Handling

449

450

```java

451

public class RobustCacheService {

452

private final AsyncCacheApi primaryCache;

453

private final AsyncCacheApi fallbackCache;

454

455

@Inject

456

public RobustCacheService(

457

@NamedCache("primary") AsyncCacheApi primaryCache,

458

@NamedCache("fallback") AsyncCacheApi fallbackCache

459

) {

460

this.primaryCache = primaryCache;

461

this.fallbackCache = fallbackCache;

462

}

463

464

public CompletionStage<Optional<Data>> getWithFallback(String key) {

465

return primaryCache.getOptional(key)

466

.exceptionally(throwable -> {

467

// Log primary cache failure

468

logger.warn("Primary cache failed, trying fallback", throwable);

469

return Optional.<Data>empty();

470

})

471

.thenCompose(result -> {

472

if (result.isPresent()) {

473

return CompletableFuture.completedFuture(result);

474

}

475

// Try fallback cache

476

return fallbackCache.getOptional(key);

477

});

478

}

479

}

480

```