From 9891c2e17b131ba16c168059c4881f9bfb3b2efc Mon Sep 17 00:00:00 2001 From: waisuan Date: Wed, 28 Oct 2015 23:55:47 +0800 Subject: [PATCH] Issue #273:Caching Patterns [new pattern] --- caching/.gitignore | 1 + caching/etc/caching.png | Bin 0 -> 51452 bytes caching/etc/caching.ucls | 106 +++++++++++++ caching/index.md | 24 +++ caching/pom.xml | 51 ++++++ .../src/main/java/com/wssia/caching/App.java | 100 ++++++++++++ .../java/com/wssia/caching/AppManager.java | 65 ++++++++ .../java/com/wssia/caching/CacheStore.java | 104 +++++++++++++ .../java/com/wssia/caching/CachingPolicy.java | 20 +++ .../java/com/wssia/caching/DBManager.java | 92 +++++++++++ .../main/java/com/wssia/caching/LRUCache.java | 146 ++++++++++++++++++ .../java/com/wssia/caching/UserAccount.java | 47 ++++++ .../test/java/com/wssia/caching/AppTest.java | 41 +++++ pom.xml | 1 + 14 files changed, 798 insertions(+) create mode 100644 caching/.gitignore create mode 100644 caching/etc/caching.png create mode 100644 caching/etc/caching.ucls create mode 100644 caching/index.md create mode 100644 caching/pom.xml create mode 100644 caching/src/main/java/com/wssia/caching/App.java create mode 100644 caching/src/main/java/com/wssia/caching/AppManager.java create mode 100644 caching/src/main/java/com/wssia/caching/CacheStore.java create mode 100644 caching/src/main/java/com/wssia/caching/CachingPolicy.java create mode 100644 caching/src/main/java/com/wssia/caching/DBManager.java create mode 100644 caching/src/main/java/com/wssia/caching/LRUCache.java create mode 100644 caching/src/main/java/com/wssia/caching/UserAccount.java create mode 100644 caching/src/test/java/com/wssia/caching/AppTest.java diff --git a/caching/.gitignore b/caching/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/caching/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/caching/etc/caching.png b/caching/etc/caching.png new file mode 100644 index 0000000000000000000000000000000000000000..e69df17ea47351789cce03c927b20c2c0ab88be1 GIT binary patch literal 51452 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/caching/index.md b/caching/index.md new file mode 100644 index 000000000..f79f13e42 --- /dev/null +++ b/caching/index.md @@ -0,0 +1,24 @@ +--- +layout: pattern +title: Caching +folder: caching +permalink: /patterns/caching/ +categories: Other +tags: + - Java +--- + +**Intent:** 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. + +![alt text](./etc/caching.png "Caching") + +**Applicability:** Use the Caching pattern(s) when + +* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead. + +**Credits** + +* [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) diff --git a/caching/pom.xml b/caching/pom.xml new file mode 100644 index 000000000..180320eea --- /dev/null +++ b/caching/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.7.0 + + caching + + + junit + junit + test + + + org.mongodb + mongodb-driver + 3.0.4 + + + org.mongodb + mongodb-driver-core + 3.0.4 + + + org.mongodb + bson + 3.0.4 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19 + + true + + + + + diff --git a/caching/src/main/java/com/wssia/caching/App.java b/caching/src/main/java/com/wssia/caching/App.java new file mode 100644 index 000000000..afdbd7d58 --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/App.java @@ -0,0 +1,100 @@ +package main.java.com.wssia.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: + * 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-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. + *

+ * 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 + * 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 + * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager + * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store + * (depending on the preferred caching policy/strategy). + * + * App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager + *

+ * + * @see CacheStore + * @See LRUCache + * @see CachingPolicy + * + */ +public class App { + + /** + * Read-through and write-through + */ + public void useReadAndWriteThroughStrategy() { + System.out.println("# CachingPolicy.THROUGH"); + AppManager.initCachingPolicy(CachingPolicy.THROUGH); + + UserAccount userAccount1 = new UserAccount("001", "John", "He is a boy."); + + AppManager.save(userAccount1); + System.out.println(AppManager.printCacheContent()); + userAccount1 = AppManager.find("001"); + userAccount1 = AppManager.find("001"); + } + + /** + * Read-through and write-around + */ + public void useReadThroughAndWriteAroundStrategy() { + System.out.println("# CachingPolicy.AROUND"); + AppManager.initCachingPolicy(CachingPolicy.AROUND); + + UserAccount userAccount2 = new UserAccount("002", "Jane", "She is a girl."); + + AppManager.save(userAccount2); + System.out.println(AppManager.printCacheContent()); + userAccount2 = AppManager.find("002"); + System.out.println(AppManager.printCacheContent()); + userAccount2 = AppManager.find("002"); + userAccount2.setUserName("Jane G."); + AppManager.save(userAccount2); + System.out.println(AppManager.printCacheContent()); + userAccount2 = AppManager.find("002"); + System.out.println(AppManager.printCacheContent()); + userAccount2 = AppManager.find("002"); + } + + /** + * Read-through and write-behind + */ + public void useReadThroughAndWriteBehindStrategy() { + System.out.println("# CachingPolicy.BEHIND"); + AppManager.initCachingPolicy(CachingPolicy.BEHIND); + + 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()); + userAccount3 = AppManager.find("003"); + System.out.println(AppManager.printCacheContent()); + UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); + AppManager.save(userAccount6); + System.out.println(AppManager.printCacheContent()); + userAccount4 = AppManager.find("004"); + System.out.println(AppManager.printCacheContent()); + } +} diff --git a/caching/src/main/java/com/wssia/caching/AppManager.java b/caching/src/main/java/com/wssia/caching/AppManager.java new file mode 100644 index 000000000..96b5fbdc8 --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/AppManager.java @@ -0,0 +1,65 @@ +package main.java.com.wssia.caching; + +import java.text.ParseException; + +/** + * + * AppManager helps to bridge the gap in communication between the main class and the application's + * back-end. DB connection is initialized through this class. The chosen caching strategy/policy is + * also initialized here. Before the cache can be used, the size of the cache has to be set. + * Depending on the chosen caching policy, AppManager will call the appropriate function in the + * CacheStore class. + * + */ +public class AppManager { + + private static CachingPolicy cachingPolicy; + + public static void init() { + try { + DBManager.connect(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + 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(); + } + })); + } + CacheStore.clearCache(); + } + + public static void initCacheCapacity(int capacity) { + CacheStore.initCapacity(capacity); + } + + public static UserAccount find(String userID) { + if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { + return CacheStore.readThrough(userID); + } else if (cachingPolicy == CachingPolicy.BEHIND) { + return CacheStore.readThroughWithWriteBackPolicy(userID); + } + return null; + } + + public static void save(UserAccount userAccount) { + if (cachingPolicy == CachingPolicy.THROUGH) { + CacheStore.writeThrough(userAccount); + } else if (cachingPolicy == CachingPolicy.AROUND) { + CacheStore.writeAround(userAccount); + } else if (cachingPolicy == CachingPolicy.BEHIND) { + CacheStore.writeBehind(userAccount); + } + } + + public static String printCacheContent() { + return CacheStore.print(); + } +} diff --git a/caching/src/main/java/com/wssia/caching/CacheStore.java b/caching/src/main/java/com/wssia/caching/CacheStore.java new file mode 100644 index 000000000..edb644bdb --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/CacheStore.java @@ -0,0 +1,104 @@ +package main.java.com.wssia.caching; + +import java.util.ArrayList; + +/** + * + * The caching strategies are implemented in this class. + * + */ +public class CacheStore { + + static LRUCache cache = null; + + public static void initCapacity(int capacity) { + if (null == cache) + cache = new LRUCache(capacity); + else + cache.setCapacity(capacity); + } + + public static UserAccount readThrough(String userID) { + if (cache.contains(userID)) { + System.out.println("# Cache Hit!"); + return cache.get(userID); + } + System.out.println("# Cache Miss!"); + UserAccount userAccount = DBManager.readFromDB(userID); + cache.set(userID, userAccount); + return userAccount; + } + + public static void writeThrough(UserAccount userAccount) { + if (cache.contains(userAccount.getUserID())) { + DBManager.updateDB(userAccount); + } else { + DBManager.writeToDB(userAccount); + } + cache.set(userAccount.getUserID(), userAccount); + } + + public static void writeAround(UserAccount userAccount) { + if (cache.contains(userAccount.getUserID())) { + DBManager.updateDB(userAccount); + cache.invalidate(userAccount.getUserID()); // Cache data has been updated -- remove older + // version from cache. + } else { + DBManager.writeToDB(userAccount); + } + } + + public static UserAccount readThroughWithWriteBackPolicy(String userID) { + if (cache.contains(userID)) { + System.out.println("# Cache Hit!"); + return cache.get(userID); + } + System.out.println("# Cache Miss!"); + UserAccount userAccount = DBManager.readFromDB(userID); + if (cache.isFull()) { + System.out.println("# Cache is FULL! Writing LRU data to DB..."); + UserAccount toBeWrittenToDB = cache.getLRUData(); + DBManager.upsertDB(toBeWrittenToDB); + } + cache.set(userID, userAccount); + return userAccount; + } + + public static void writeBehind(UserAccount userAccount) { + if (cache.isFull() && !cache.contains(userAccount.getUserID())) { + System.out.println("# Cache is FULL! Writing LRU data to DB..."); + UserAccount toBeWrittenToDB = cache.getLRUData(); + DBManager.upsertDB(toBeWrittenToDB); + } + cache.set(userAccount.getUserID(), userAccount); + } + + public static void clearCache() { + if (null != cache) + cache.clear(); + } + + /** + * Writes remaining content in the cache into the DB. + */ + public static void flushCache() { + System.out.println("# flushCache..."); + if (null == cache) + return; + ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); + for (UserAccount userAccount : listOfUserAccounts) { + DBManager.upsertDB(userAccount); + } + } + + public static String print() { + ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); + StringBuilder sb = new StringBuilder(); + sb.append("\n--CACHE CONTENT--\n"); + for (UserAccount userAccount : listOfUserAccounts) { + sb.append(userAccount.toString() + "\n"); + } + sb.append("----\n"); + return sb.toString(); + } +} diff --git a/caching/src/main/java/com/wssia/caching/CachingPolicy.java b/caching/src/main/java/com/wssia/caching/CachingPolicy.java new file mode 100644 index 000000000..ee51c0361 --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/CachingPolicy.java @@ -0,0 +1,20 @@ +package main.java.com.wssia.caching; + +/** + * + * Enum class containing the three caching strategies implemented in the pattern. + * + */ +public enum CachingPolicy { + THROUGH("through"), AROUND("around"), BEHIND("behind"); + + private String policy; + + private CachingPolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return policy; + } +} diff --git a/caching/src/main/java/com/wssia/caching/DBManager.java b/caching/src/main/java/com/wssia/caching/DBManager.java new file mode 100644 index 000000000..20918b22f --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/DBManager.java @@ -0,0 +1,92 @@ +package main.java.com.wssia.caching; + +import java.text.ParseException; + +import org.bson.Document; + +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.UpdateOptions; + +/** + * + * DBManager handles the communication with the underlying data store i.e. Database. It contains the + * implemented methods for querying, inserting, and updating data. MongoDB was used as the database + * for the application. + * + */ +public class DBManager { + + private static MongoClient mongoClient; + private static MongoDatabase db; + + public static void connect() throws ParseException { + mongoClient = new MongoClient(); + db = mongoClient.getDatabase("test"); + } + + public static UserAccount readFromDB(String userID) { + if (null == db) { + try { + connect(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + FindIterable iterable = + db.getCollection("user_accounts").find(new Document("userID", userID)); + if (iterable == null) + return null; + Document doc = iterable.first(); + UserAccount userAccount = + new UserAccount(userID, doc.getString("userName"), doc.getString("additionalInfo")); + return userAccount; + } + + public static void writeToDB(UserAccount userAccount) { + if (null == db) { + try { + connect(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + db.getCollection("user_accounts").insertOne( + new Document("userID", userAccount.getUserID()).append("userName", + userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())); + } + + public static void updateDB(UserAccount userAccount) { + if (null == db) { + try { + connect(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + db.getCollection("user_accounts").updateOne( + new Document("userID", userAccount.getUserID()), + new Document("$set", new Document("userName", userAccount.getUserName()).append( + "additionalInfo", userAccount.getAdditionalInfo()))); + } + + /** + * + * Insert data into DB if it does not exist. Else, update it. + */ + public static void upsertDB(UserAccount userAccount) { + if (null == db) { + try { + connect(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + db.getCollection("user_accounts").updateOne( + new Document("userID", userAccount.getUserID()), + new Document("$set", new Document("userID", userAccount.getUserID()).append("userName", + userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())), + new UpdateOptions().upsert(true)); + } +} diff --git a/caching/src/main/java/com/wssia/caching/LRUCache.java b/caching/src/main/java/com/wssia/caching/LRUCache.java new file mode 100644 index 000000000..c69e7e3fd --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/LRUCache.java @@ -0,0 +1,146 @@ +package main.java.com.wssia.caching; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * + * Data structure/implementation of the application's cache. The data structure consists of a hash + * table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the + * LRU data in the cache. When a data is queried (from the cache), added (to the cache), or updated, + * the data is moved to the front of the list to depict itself as the most-recently-used data. The + * LRU data is always at the end of the list. + * + */ +public class LRUCache { + + class Node { + String userID; + UserAccount userAccount; + Node previous; + Node next; + + public Node(String userID, UserAccount userAccount) { + this.userID = userID; + this.userAccount = userAccount; + } + } + + int capacity; + HashMap cache = new HashMap(); + Node head = null; + Node end = null; + + public LRUCache(int capacity) { + this.capacity = capacity; + } + + public UserAccount get(String userID) { + if (cache.containsKey(userID)) { + Node node = cache.get(userID); + remove(node); + setHead(node); + return node.userAccount; + } + return null; + } + + /** + * + * Remove node from linked list. + */ + public void remove(Node node) { + if (node.previous != null) { + node.previous.next = node.next; + } else { + head = node.next; + } + if (node.next != null) { + node.next.previous = node.previous; + } else { + end = node.previous; + } + } + + /** + * + * Move node to the front of the list. + */ + public void setHead(Node node) { + node.next = head; + node.previous = null; + if (head != null) + head.previous = node; + head = node; + if (end == null) + end = head; + } + + public void set(String userID, UserAccount userAccount) { + if (cache.containsKey(userID)) { + Node old = cache.get(userID); + old.userAccount = userAccount; + remove(old); + setHead(old); + } else { + Node newNode = new Node(userID, userAccount); + if (cache.size() >= capacity) { + System.out.println("# Cache is FULL! Removing " + end.userID + " from cache..."); + cache.remove(end.userID); // remove LRU data from cache. + remove(end); + setHead(newNode); + } else { + setHead(newNode); + } + cache.put(userID, newNode); + } + } + + public boolean contains(String userID) { + return cache.containsKey(userID); + } + + 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); + } + + public boolean isFull() { + return cache.size() >= capacity; + } + + public UserAccount getLRUData() { + return end.userAccount; + } + + public void clear() { + head = null; + end = null; + cache.clear(); + } + + /** + * + * Returns cache data in list form. + */ + public ArrayList getCacheDataInListForm() { + ArrayList listOfCacheData = new ArrayList(); + Node temp = head; + while (temp != null) { + listOfCacheData.add(temp.userAccount); + temp = temp.next; + } + return listOfCacheData; + } + + public void setCapacity(int newCapacity) { + if (capacity > newCapacity) { + clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll + // just clear the cache. + } else { + this.capacity = newCapacity; + } + } +} diff --git a/caching/src/main/java/com/wssia/caching/UserAccount.java b/caching/src/main/java/com/wssia/caching/UserAccount.java new file mode 100644 index 000000000..a9fe36f7a --- /dev/null +++ b/caching/src/main/java/com/wssia/caching/UserAccount.java @@ -0,0 +1,47 @@ +package main.java.com.wssia.caching; + +/** + * + * Entity class (stored in cache and DB) used in the application. + * + */ +public class UserAccount { + private String userID; + private String userName; + private String additionalInfo; + + public UserAccount(String userID, String userName, String additionalInfo) { + this.userID = userID; + this.userName = userName; + this.additionalInfo = additionalInfo; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(String additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public String toString() { + return userID + ", " + userName + ", " + additionalInfo; + } +} diff --git a/caching/src/test/java/com/wssia/caching/AppTest.java b/caching/src/test/java/com/wssia/caching/AppTest.java new file mode 100644 index 000000000..3a93afecc --- /dev/null +++ b/caching/src/test/java/com/wssia/caching/AppTest.java @@ -0,0 +1,41 @@ +package test.java.com.wssia.caching; + +import main.java.com.wssia.caching.App; +import main.java.com.wssia.caching.AppManager; + +import org.junit.Before; +import org.junit.Test; + +/** + * + * Application test + * + */ +public class AppTest { + App app; + + /** + * Setup of application test includes: initializing DB connection and cache size/capacity. + */ + @Before + public void setUp() { + AppManager.init(); + AppManager.initCacheCapacity(3); + app = new App(); + } + + @Test + public void testReadAndWriteThroughStrategy() { + app.useReadAndWriteThroughStrategy(); + } + + @Test + public void testReadThroughAndWriteAroundStrategy() { + app.useReadThroughAndWriteAroundStrategy(); + } + + @Test + public void testReadThroughAndWriteBehindStrategy() { + app.useReadThroughAndWriteBehindStrategy(); + } +} diff --git a/pom.xml b/pom.xml index 4cb30df3f..36e43727b 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,7 @@ message-channel fluentinterface reactor + caching