diff --git a/caching/README.md b/caching/README.md index 6432ffcea..b48d060f0 100644 --- a/caching/README.md +++ b/caching/README.md @@ -27,3 +27,4 @@ Use the Caching pattern(s) when * [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) * [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177) +* [Cache-Aside](https://msdn.microsoft.com/en-us/library/dn589799.aspx) diff --git a/caching/etc/caching.png b/caching/etc/caching.png index 6b3b2d055..b6ed703ab 100644 Binary files a/caching/etc/caching.png and b/caching/etc/caching.png differ diff --git a/caching/etc/caching.ucls b/caching/etc/caching.ucls index 815a62ec9..a058277ea 100644 --- a/caching/etc/caching.ucls +++ b/caching/etc/caching.ucls @@ -1,106 +1,90 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index 8e5a84085..1b1b5e1ca 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -26,22 +26,23 @@ package com.iluwatar.caching; * * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing * the resources immediately after their use. The resources retain their identity, are kept in some - * fast-access storage, and are re-used to avoid having to acquire them again. There are three main - * caching strategies/techniques in this pattern; each with their own pros and cons. They are: + * fast-access storage, and are re-used to avoid having to acquire them again. There are four main + * caching strategies/techniques in this pattern; each with their own pros and cons. They are; * write-through which writes data to the cache and DB in a single transaction, - * write-around which writes data immediately into the DB instead of the cache, and + * write-around which writes data immediately into the DB instead of the cache, * write-behind which writes data into the cache initially whilst the data is only - * written into the DB when the cache is full. The read-through strategy is also - * included in the mentioned three strategies -- returns data from the cache to the caller if - * it exists else queries from DB and stores it into the cache for future use. These - * strategies determine when the data in the cache should be written back to the backing store (i.e. - * Database) and help keep both data sources synchronized/up-to-date. This pattern can improve - * performance and also helps to maintain consistency between data held in the cache and the data in - * the underlying data store. + * written into the DB when the cache is full, and cache-aside which pushes the + * responsibility of keeping the data synchronized in both data sources to the application itself. + * The read-through strategy is also included in the mentioned four strategies -- + * returns data from the cache to the caller if it exists else queries from DB and + * stores it into the cache for future use. These strategies determine when the data in the cache + * should be written back to the backing store (i.e. Database) and help keep both data sources + * synchronized/up-to-date. This pattern can improve performance and also helps to maintain + * consistency between data held in the cache and the data in the underlying data store. *

* In this example, the user account ({@link UserAccount}) entity is used as the underlying * application data. The cache itself is implemented as an internal (Java) data structure. It adopts - * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three + * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four * strategies are individually tested. The testing of the cache is restricted towards saving and * querying of user accounts from the underlying data store ( {@link DbManager}). The main class ( * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and @@ -74,6 +75,7 @@ public class App { app.useReadAndWriteThroughStrategy(); app.useReadThroughAndWriteAroundStrategy(); app.useReadThroughAndWriteBehindStrategy(); + app.useCacheAsideStategy(); } /** @@ -136,4 +138,26 @@ public class App { AppManager.find("004"); System.out.println(AppManager.printCacheContent()); } + + /** + * Cache-Aside + */ + public void useCacheAsideStategy() { + System.out.println("# CachingPolicy.ASIDE"); + AppManager.initCachingPolicy(CachingPolicy.ASIDE); + System.out.println(AppManager.printCacheContent()); + + UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food."); + UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); + AppManager.save(userAccount3); + AppManager.save(userAccount4); + AppManager.save(userAccount5); + + System.out.println(AppManager.printCacheContent()); + AppManager.find("003"); + System.out.println(AppManager.printCacheContent()); + AppManager.find("004"); + System.out.println(AppManager.printCacheContent()); + } } diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 2967c759f..2d6a424a3 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -64,12 +64,7 @@ public final class AppManager { public static void initCachingPolicy(CachingPolicy policy) { cachingPolicy = policy; if (cachingPolicy == CachingPolicy.BEHIND) { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - CacheStore.flushCache(); - } - })); + Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache)); } CacheStore.clearCache(); } @@ -86,6 +81,8 @@ public final class AppManager { return CacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { return CacheStore.readThroughWithWriteBackPolicy(userId); + } else if (cachingPolicy == CachingPolicy.ASIDE) { + return findAside(userId); } return null; } @@ -100,10 +97,37 @@ public final class AppManager { CacheStore.writeAround(userAccount); } else if (cachingPolicy == CachingPolicy.BEHIND) { CacheStore.writeBehind(userAccount); + } else if (cachingPolicy == CachingPolicy.ASIDE) { + saveAside(userAccount); } } public static String printCacheContent() { return CacheStore.print(); } + + /** + * Cache-Aside save user account helper + */ + private static void saveAside(UserAccount userAccount) { + DbManager.updateDb(userAccount); + CacheStore.invalidate(userAccount.getUserId()); + } + + /** + * Cache-Aside find user account helper + */ + private static UserAccount findAside(String userId) { + UserAccount userAccount = CacheStore.get(userId); + if (userAccount != null) { + return userAccount; + } + + userAccount = DbManager.readFromDb(userId); + if (userAccount != null) { + CacheStore.set(userId, userAccount); + } + + return userAccount; + } } diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 5903f8219..c2b95a1bd 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -40,7 +40,7 @@ public class CacheStore { * Init cache capacity */ public static void initCapacity(int capacity) { - if (null == cache) { + if (cache == null) { cache = new LruCache(capacity); } else { cache.setCapacity(capacity); @@ -121,7 +121,7 @@ public class CacheStore { * Clears cache */ public static void clearCache() { - if (null != cache) { + if (cache != null) { cache.clear(); } } @@ -153,4 +153,25 @@ public class CacheStore { sb.append("----\n"); return sb.toString(); } + + /** + * Delegate to backing cache store + */ + public static UserAccount get(String userId) { + return cache.get(userId); + } + + /** + * Delegate to backing cache store + */ + public static void set(String userId, UserAccount userAccount) { + cache.set(userId, userAccount); + } + + /** + * Delegate to backing cache store + */ + public static void invalidate(String userId) { + cache.invalidate(userId); + } } diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java index 490113baa..5ad32f699 100644 --- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java +++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java @@ -24,11 +24,11 @@ package com.iluwatar.caching; /** * - * Enum class containing the three caching strategies implemented in the pattern. + * Enum class containing the four caching strategies implemented in the pattern. * */ public enum CachingPolicy { - THROUGH("through"), AROUND("around"), BEHIND("behind"); + THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside"); private String policy; diff --git a/caching/src/main/java/com/iluwatar/caching/DbManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java index c12461d0c..455302106 100644 --- a/caching/src/main/java/com/iluwatar/caching/DbManager.java +++ b/caching/src/main/java/com/iluwatar/caching/DbManager.java @@ -82,7 +82,7 @@ public final class DbManager { } return null; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -106,7 +106,7 @@ public final class DbManager { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -126,7 +126,7 @@ public final class DbManager { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -148,7 +148,7 @@ public final class DbManager { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index 5c5549afd..00e27ccf1 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -73,7 +73,6 @@ public class LruCache { } /** - * * Remove node from linked list. */ public void remove(Node node) { @@ -90,7 +89,6 @@ public class LruCache { } /** - * * Move node to the front of the list. */ public void setHead(Node node) { @@ -136,10 +134,11 @@ public class LruCache { * Invalidate cache for user */ public void invalidate(String userId) { - System.out.println("# " + userId + " has been updated! Removing older version from cache..."); - Node toBeRemoved = cache.get(userId); - remove(toBeRemoved); - cache.remove(userId); + Node toBeRemoved = cache.remove(userId); + if (toBeRemoved != null) { + System.out.println("# " + userId + " has been updated! Removing older version from cache..."); + remove(toBeRemoved); + } } public boolean isFull() { @@ -160,7 +159,6 @@ public class LruCache { } /** - * * Returns cache data in list form. */ public List getCacheDataInListForm() { diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index e2444ad72..657f12fa3 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -23,9 +23,7 @@ package com.iluwatar.caching; /** - * * Entity class (stored in cache and DB) used in the application. - * */ public class UserAccount { private String userId; diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 19262a3b6..9d2299902 100644 --- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java +++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java @@ -60,4 +60,9 @@ public class CachingTest { public void testReadThroughAndWriteBehindStrategy() { app.useReadThroughAndWriteBehindStrategy(); } + + @Test + public void testCacheAsideStrategy() { + app.useCacheAsideStategy(); + } }