Implement cache entry expiration for CachedWeatherForecaster.
Co-Authored-By: os222
This commit is contained in:
parent
30cd615adb
commit
5f731c1ab2
|
@ -1,5 +1,6 @@
|
||||||
package ic.doc;
|
package ic.doc;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -8,33 +9,43 @@ import java.util.Queue;
|
||||||
public class CachedWeatherForecaster implements WeatherForecaster {
|
public class CachedWeatherForecaster implements WeatherForecaster {
|
||||||
private final WeatherForecaster forecaster;
|
private final WeatherForecaster forecaster;
|
||||||
private final Integer maxCacheSize;
|
private final Integer maxCacheSize;
|
||||||
private final HashMap<CacheKey, WeatherForecast> cache = new HashMap<>();
|
private final Integer maxCacheAgeSecs;
|
||||||
|
private final HashMap<CacheKey, CacheEntry> cache = new HashMap<>();
|
||||||
private final Queue<CacheKey> evictionQueue = new ArrayDeque<>();
|
private final Queue<CacheKey> evictionQueue = new ArrayDeque<>();
|
||||||
|
|
||||||
CachedWeatherForecaster(WeatherForecaster forecaster, Integer maxCacheSize) {
|
CachedWeatherForecaster(WeatherForecaster forecaster, Integer maxCacheSize,
|
||||||
|
Integer maxCacheAgeSecs) {
|
||||||
this.forecaster = forecaster;
|
this.forecaster = forecaster;
|
||||||
this.maxCacheSize = maxCacheSize;
|
this.maxCacheSize = maxCacheSize;
|
||||||
|
this.maxCacheAgeSecs = maxCacheAgeSecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WeatherForecast forecastFor(WeatherRegion region, Weekday day) {
|
public WeatherForecast forecastFor(WeatherRegion region, Weekday day) {
|
||||||
|
Instant now = Instant.now();
|
||||||
CacheKey key = new CacheKey(region, day);
|
CacheKey key = new CacheKey(region, day);
|
||||||
WeatherForecast forecast = cache.get(key);
|
CacheEntry entry = cache.get(key);
|
||||||
if (forecast == null) {
|
if (entry == null || entry.isExpired(now, maxCacheAgeSecs)) {
|
||||||
forecast = forecaster.forecastFor(region, day);
|
WeatherForecast forecast = forecaster.forecastFor(region, day);
|
||||||
putCache(key, forecast);
|
putCache(key, new CacheEntry(now, forecast));
|
||||||
return forecast;
|
return forecast;
|
||||||
}
|
}
|
||||||
return forecast;
|
return entry.forecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putCache(CacheKey key, WeatherForecast forecast) {
|
private void putCache(CacheKey key, CacheEntry entry) {
|
||||||
if (maxCacheSize != null && cache.size() >= maxCacheSize) {
|
if (maxCacheSize != null && cache.size() >= maxCacheSize) {
|
||||||
cache.remove(evictionQueue.poll());
|
cache.remove(evictionQueue.poll());
|
||||||
}
|
}
|
||||||
cache.put(key, forecast);
|
cache.put(key, entry);
|
||||||
evictionQueue.add(key);
|
evictionQueue.add(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CacheKey(WeatherRegion region, Weekday day) {}
|
private record CacheKey(WeatherRegion region, Weekday day) {}
|
||||||
|
|
||||||
|
private record CacheEntry(Instant timestamp, WeatherForecast forecast) {
|
||||||
|
boolean isExpired(Instant now, Integer maxCacheAgeSecs) {
|
||||||
|
return maxCacheAgeSecs != null && timestamp.plusSeconds(maxCacheAgeSecs).isBefore(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package ic.doc;
|
||||||
public class CachedWeatherForecasterBuilder {
|
public class CachedWeatherForecasterBuilder {
|
||||||
private WeatherForecaster forecaster;
|
private WeatherForecaster forecaster;
|
||||||
private Integer maxCacheSize = null;
|
private Integer maxCacheSize = null;
|
||||||
|
private Integer maxCacheAgeSecs = null;
|
||||||
|
|
||||||
private CachedWeatherForecasterBuilder() {}
|
private CachedWeatherForecasterBuilder() {}
|
||||||
|
|
||||||
|
@ -20,7 +21,12 @@ public class CachedWeatherForecasterBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CachedWeatherForecasterBuilder withMaxCacheAgeSecs(int maxCacheAgeSecs) {
|
||||||
|
this.maxCacheAgeSecs = maxCacheAgeSecs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CachedWeatherForecaster build() {
|
public CachedWeatherForecaster build() {
|
||||||
return new CachedWeatherForecaster(forecaster, maxCacheSize);
|
return new CachedWeatherForecaster(forecaster, maxCacheSize, maxCacheAgeSecs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,4 +85,31 @@ public class CachedWeatherForecasterTest {
|
||||||
actForecast = cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
actForecast = cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
assertEquals(expForecast, actForecast);
|
assertEquals(expForecast, actForecast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forecastForCacheExpires() {
|
||||||
|
WeatherForecaster cachedForecaster = cachedWeatherForecaster.withMaxCacheAgeSecs(1).build();
|
||||||
|
WeatherForecast forecast1 = new WeatherForecast("Sunny", 20);
|
||||||
|
WeatherForecast forecast2 = new WeatherForecast("Rainy", 10);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
will(returnValue(forecast1));
|
||||||
|
oneOf(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
will(returnValue(forecast2));
|
||||||
|
}});
|
||||||
|
// Cache put and immediate hit
|
||||||
|
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
WeatherForecast actForecast =
|
||||||
|
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
assertEquals(forecast1, actForecast);
|
||||||
|
// Cache expires, miss, then hit
|
||||||
|
try {
|
||||||
|
Thread.sleep(1100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
fail("Interrupted while sleeping: " + e);
|
||||||
|
}
|
||||||
|
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
actForecast = cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
|
||||||
|
assertEquals(forecast2, actForecast);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue