209 lines
7.6 KiB
Java
209 lines
7.6 KiB
Java
package com.iluwatar.caching;
|
|
|
|
import com.iluwatar.caching.database.DbManager;
|
|
import com.iluwatar.caching.database.DbManagerFactory;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
/**
|
|
* 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 four main
|
|
* caching strategies/techniques in this pattern; each with their own pros and
|
|
* cons. They are <code>write-through</code> which writes data to the cache and
|
|
* DB in a single transaction, <code>write-around</code> which writes data
|
|
* immediately into the DB instead of the cache, <code>write-behind</code>
|
|
* which writes data into the cache initially whilst the data is only
|
|
* written into the DB when the cache is full, and <code>cache-aside</code>
|
|
* which pushes the responsibility of keeping the data synchronized in both
|
|
* data sources to the application itself. The <code>read-through</code>
|
|
* strategy is also included in the mentioned four strategies --
|
|
* returns data from the cache to the caller <b>if</b> it exists <b>else</b>
|
|
* 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 maintainconsistency between data held in the cache and the data in
|
|
* the underlying data store.
|
|
*
|
|
* <p>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 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 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).
|
|
* <p>
|
|
* <i>{@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy -->
|
|
* DBManager} </i>
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* To run the application with MongoDb, just start it with parameter --mongo
|
|
* Example: java -jar app.jar --mongo
|
|
* </p>
|
|
*
|
|
* @see CacheStore
|
|
* @see LruCache
|
|
* @see CachingPolicy
|
|
*/
|
|
@Slf4j
|
|
public class App {
|
|
/**
|
|
* Constant parameter name to use mongoDB.
|
|
*/
|
|
private static final String USE_MONGO_DB = "--mongo";
|
|
/**
|
|
* Application manager.
|
|
*/
|
|
private final AppManager appManager;
|
|
|
|
/**
|
|
* Constructor of current App.
|
|
* @param isMongo boolean
|
|
*/
|
|
public App(final boolean isMongo) {
|
|
DbManager dbManager = DbManagerFactory.initDb(isMongo);
|
|
appManager = new AppManager(dbManager);
|
|
appManager.initDb();
|
|
}
|
|
|
|
/**
|
|
* Program entry point.
|
|
*
|
|
* @param args command line args
|
|
*/
|
|
public static void main(final String[] args) {
|
|
// 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).
|
|
App app = new App(isDbMongo(args));
|
|
app.useReadAndWriteThroughStrategy();
|
|
String splitLine = "==============================================";
|
|
LOGGER.info(splitLine);
|
|
app.useReadThroughAndWriteAroundStrategy();
|
|
LOGGER.info(splitLine);
|
|
app.useReadThroughAndWriteBehindStrategy();
|
|
LOGGER.info(splitLine);
|
|
app.useCacheAsideStategy();
|
|
LOGGER.info(splitLine);
|
|
}
|
|
|
|
/**
|
|
* Check the input parameters. if
|
|
* @param args input params
|
|
* @return true if there is "--mongo" parameter in arguments
|
|
*/
|
|
private static boolean isDbMongo(final String[] args) {
|
|
for (String arg : args) {
|
|
if (arg.equals(USE_MONGO_DB)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Read-through and write-through.
|
|
*/
|
|
public void useReadAndWriteThroughStrategy() {
|
|
LOGGER.info("# CachingPolicy.THROUGH");
|
|
appManager.initCachingPolicy(CachingPolicy.THROUGH);
|
|
|
|
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
|
|
|
|
appManager.save(userAccount1);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("001");
|
|
appManager.find("001");
|
|
}
|
|
|
|
/**
|
|
* Read-through and write-around.
|
|
*/
|
|
public void useReadThroughAndWriteAroundStrategy() {
|
|
LOGGER.info("# CachingPolicy.AROUND");
|
|
appManager.initCachingPolicy(CachingPolicy.AROUND);
|
|
|
|
var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
|
|
|
|
appManager.save(userAccount2);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("002");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
userAccount2 = appManager.find("002");
|
|
userAccount2.setUserName("Jane G.");
|
|
appManager.save(userAccount2);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("002");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("002");
|
|
}
|
|
|
|
/**
|
|
* Read-through and write-behind.
|
|
*/
|
|
public void useReadThroughAndWriteBehindStrategy() {
|
|
LOGGER.info("# CachingPolicy.BEHIND");
|
|
appManager.initCachingPolicy(CachingPolicy.BEHIND);
|
|
|
|
var userAccount3 = new UserAccount("003",
|
|
"Adam",
|
|
"He likes food.");
|
|
var userAccount4 = new UserAccount("004",
|
|
"Rita",
|
|
"She hates cats.");
|
|
var userAccount5 = new UserAccount("005",
|
|
"Isaac",
|
|
"He is allergic to mustard.");
|
|
|
|
appManager.save(userAccount3);
|
|
appManager.save(userAccount4);
|
|
appManager.save(userAccount5);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("003");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
UserAccount userAccount6 = new UserAccount("006",
|
|
"Yasha",
|
|
"She is an only child.");
|
|
appManager.save(userAccount6);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("004");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
}
|
|
|
|
/**
|
|
* Cache-Aside.
|
|
*/
|
|
public void useCacheAsideStategy() {
|
|
LOGGER.info("# CachingPolicy.ASIDE");
|
|
appManager.initCachingPolicy(CachingPolicy.ASIDE);
|
|
LOGGER.info(appManager.printCacheContent());
|
|
|
|
var userAccount3 = new UserAccount("003",
|
|
"Adam",
|
|
"He likes food.");
|
|
var userAccount4 = new UserAccount("004",
|
|
"Rita",
|
|
"She hates cats.");
|
|
var userAccount5 = new UserAccount("005",
|
|
"Isaac",
|
|
"He is allergic to mustard.");
|
|
appManager.save(userAccount3);
|
|
appManager.save(userAccount4);
|
|
appManager.save(userAccount5);
|
|
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("003");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
appManager.find("004");
|
|
LOGGER.info(appManager.printCacheContent());
|
|
}
|
|
}
|