-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #277 from waisuan/master
Issue #273: Caching Patterns [new pattern]
- Loading branch information
Showing
14 changed files
with
856 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target/ |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true" | ||
realizations="true" associations="true" dependencies="false" nesting-relationships="true"> | ||
<class id="1" language="java" name="main.java.com.wssia.caching.App" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/App.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="249" y="150"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<class id="2" language="java" name="main.java.com.wssia.caching.AppManager" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/AppManager.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="502" y="163"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<class id="3" language="java" name="main.java.com.wssia.caching.CacheStore" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/CacheStore.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="537" y="436"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<enumeration id="4" language="java" name="main.java.com.wssia.caching.CachingPolicy" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/CachingPolicy.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="789" y="162"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</enumeration> | ||
<class id="5" language="java" name="main.java.com.wssia.caching.DBManager" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/DBManager.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="1137" y="134"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<class id="6" language="java" name="main.java.com.wssia.caching.LRUCache" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/LRUCache.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="884" y="435"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<class id="7" language="java" name="main.java.com.wssia.caching.UserAccount" project="CachingPatterns" | ||
file="/CachingPatterns/src/main/java/com/wssia/caching/UserAccount.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="1140" y="405"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<class id="8" language="java" name="test.java.com.wssia.caching.AppTest" project="CachingPatterns" | ||
file="/CachingPatterns/src/test/java/com/wssia/caching/AppTest.java" binary="false" corner="BOTTOM_RIGHT"> | ||
<position height="-1" width="-1" x="251" y="374"/> | ||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</display> | ||
</class> | ||
<association id="9"> | ||
<end type="SOURCE" refId="2" navigable="false"> | ||
<attribute id="10" name="cachingPolicy"/> | ||
<multiplicity id="11" minimum="0" maximum="1"/> | ||
</end> | ||
<end type="TARGET" refId="4" navigable="true"/> | ||
<display labels="true" multiplicity="true"/> | ||
</association> | ||
<association id="12"> | ||
<end type="SOURCE" refId="8" navigable="false"> | ||
<attribute id="13" name="app"/> | ||
<multiplicity id="14" minimum="0" maximum="1"/> | ||
</end> | ||
<end type="TARGET" refId="1" navigable="true"/> | ||
<display labels="true" multiplicity="true"/> | ||
</association> | ||
<association id="15"> | ||
<end type="SOURCE" refId="3" navigable="false"> | ||
<attribute id="16" name="cache"/> | ||
<multiplicity id="17" minimum="0" maximum="1"/> | ||
</end> | ||
<end type="TARGET" refId="6" navigable="true"/> | ||
<display labels="true" multiplicity="true"/> | ||
</association> | ||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" | ||
sort-features="false" accessors="true" visibility="true"> | ||
<attributes public="true" package="true" protected="true" private="true" static="true"/> | ||
<operations public="true" package="true" protected="true" private="true" static="true"/> | ||
</classifier-display> | ||
<association-display labels="true" multiplicity="true"/> | ||
</class-diagram> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?xml version="1.0"?> | ||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>com.iluwatar</groupId> | ||
<artifactId>java-design-patterns</artifactId> | ||
<version>1.7.0</version> | ||
</parent> | ||
<artifactId>caching</artifactId> | ||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mongodb</groupId> | ||
<artifactId>mongodb-driver</artifactId> | ||
<version>3.0.4</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mongodb</groupId> | ||
<artifactId>mongodb-driver-core</artifactId> | ||
<version>3.0.4</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mongodb</groupId> | ||
<artifactId>bson</artifactId> | ||
<version>3.0.4</version> | ||
</dependency> | ||
</dependencies> | ||
<!-- | ||
Due to the use of MongoDB in the test of this pattern, TRAVIS and/or MAVEN might fail if the DB connection is | ||
not open for the JUnit test. To avoid disrupting the compilation process, the surefire plug-in was used | ||
to SKIP the running of the JUnit tests for this pattern. To ACTIVATE the running of the tests, change the | ||
skipTests (below) flag to 'false' and vice-versa. | ||
--> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.19</version> | ||
<configuration> | ||
<skipTests>false</skipTests> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
* <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, and | ||
* <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. The <code>read-through</code> strategy is also | ||
* included in the mentioned three 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 maintain consistency 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 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). | ||
* | ||
* <i>App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager</i> | ||
* </p> | ||
* | ||
* @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()); | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
caching/src/main/java/com/iluwatar/caching/AppManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Oops, something went wrong.