diff --git a/caching/.gitignore b/caching/.gitignore
new file mode 100644
index 00000000..b83d2226
--- /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 00000000..6b3b2d05
Binary files /dev/null and b/caching/etc/caching.png differ
diff --git a/caching/etc/caching.ucls b/caching/etc/caching.ucls
new file mode 100644
index 00000000..815a62ec
--- /dev/null
+++ b/caching/etc/caching.ucls
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/caching/index.md b/caching/index.md
new file mode 100644
index 00000000..f79f13e4
--- /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 00000000..d2284a5f
--- /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
+
+ false
+
+
+
+
+
diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java
new file mode 100644
index 00000000..c7f55db7
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/App.java
@@ -0,0 +1,117 @@
+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:
+ * 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 {
+
+ /**
+ * Program entry point
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) {
+ AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
+ // and the App class to avoid Maven compilation errors. Set flag to
+ // true to run the tests with MongoDB (provided that MongoDB is
+ // installed and socket connection is open).
+ AppManager.initCacheCapacity(3);
+ App app = new App();
+ app.useReadAndWriteThroughStrategy();
+ app.useReadThroughAndWriteAroundStrategy();
+ app.useReadThroughAndWriteBehindStrategy();
+ }
+
+ /**
+ * 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/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
new file mode 100644
index 00000000..08132e32
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -0,0 +1,75 @@
+package com.iluwatar.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;
+
+ /**
+ *
+ * Developer/Tester is able to choose whether the application should use MongoDB as its underlying
+ * data storage or a simple Java data structure to (temporarily) store the data/objects during
+ * runtime.
+ */
+ public static void initDB(boolean useMongoDB) {
+ if (useMongoDB) {
+ try {
+ DBManager.connect();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ } else {
+ DBManager.createVirtualDB();
+ }
+ }
+
+ 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/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
new file mode 100644
index 00000000..2041ac14
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -0,0 +1,104 @@
+package com.iluwatar.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/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
new file mode 100644
index 00000000..314cfaa3
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
@@ -0,0 +1,20 @@
+package com.iluwatar.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/iluwatar/caching/DBManager.java b/caching/src/main/java/com/iluwatar/caching/DBManager.java
new file mode 100644
index 00000000..07a5daea
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/DBManager.java
@@ -0,0 +1,123 @@
+package com.iluwatar.caching;
+
+import java.text.ParseException;
+import java.util.HashMap;
+
+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.
+ *
+ * Developer/Tester is able to choose whether the application should use MongoDB as its underlying
+ * data storage (connect()) or a simple Java data structure to (temporarily) store the data/objects
+ * during runtime (createVirtualDB()).
+ *
+ */
+public class DBManager {
+
+ private static MongoClient mongoClient;
+ private static MongoDatabase db;
+ private static boolean useMongoDB;
+
+ private static HashMap virtualDB;
+
+ public static void createVirtualDB() {
+ useMongoDB = false;
+ virtualDB = new HashMap();
+ }
+
+ public static void connect() throws ParseException {
+ useMongoDB = true;
+ mongoClient = new MongoClient();
+ db = mongoClient.getDatabase("test");
+ }
+
+ public static UserAccount readFromDB(String userID) {
+ if (!useMongoDB) {
+ if (virtualDB.containsKey(userID))
+ return virtualDB.get(userID);
+ return null;
+ }
+ 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 (!useMongoDB) {
+ virtualDB.put(userAccount.getUserID(), userAccount);
+ return;
+ }
+ 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 (!useMongoDB) {
+ virtualDB.put(userAccount.getUserID(), userAccount);
+ return;
+ }
+ 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 (!useMongoDB) {
+ virtualDB.put(userAccount.getUserID(), userAccount);
+ return;
+ }
+ 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/iluwatar/caching/LRUCache.java b/caching/src/main/java/com/iluwatar/caching/LRUCache.java
new file mode 100644
index 00000000..872f9725
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/LRUCache.java
@@ -0,0 +1,146 @@
+package com.iluwatar.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/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
new file mode 100644
index 00000000..eff0878a
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
@@ -0,0 +1,47 @@
+package com.iluwatar.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/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java
new file mode 100644
index 00000000..ce5cddf0
--- /dev/null
+++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java
@@ -0,0 +1,41 @@
+package com.iluwatar.caching;
+
+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.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
+ // to avoid Maven compilation errors. Set flag to true to run the
+ // tests with MongoDB (provided that MongoDB is installed and socket
+ // connection is open).
+ 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 0222d7e3..3462cc86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,7 @@
message-channel
fluentinterface
reactor
+ caching