Skip to content

Commit a2dd87d

Browse files
committed
Merge pull request iluwatar#277 from waisuan/master
Issue iluwatar#273: Caching Patterns [new pattern]
2 parents 7d3483d + 998ba7e commit a2dd87d

File tree

14 files changed

+856
-0
lines changed

14 files changed

+856
-0
lines changed

caching/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target/

caching/etc/caching.png

54.9 KB
Loading

caching/etc/caching.ucls

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
3+
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
4+
<class id="1" language="java" name="main.java.com.wssia.caching.App" project="CachingPatterns"
5+
file="/CachingPatterns/src/main/java/com/wssia/caching/App.java" binary="false" corner="BOTTOM_RIGHT">
6+
<position height="-1" width="-1" x="249" y="150"/>
7+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
8+
sort-features="false" accessors="true" visibility="true">
9+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
10+
<operations public="true" package="true" protected="true" private="true" static="true"/>
11+
</display>
12+
</class>
13+
<class id="2" language="java" name="main.java.com.wssia.caching.AppManager" project="CachingPatterns"
14+
file="/CachingPatterns/src/main/java/com/wssia/caching/AppManager.java" binary="false" corner="BOTTOM_RIGHT">
15+
<position height="-1" width="-1" x="502" y="163"/>
16+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
17+
sort-features="false" accessors="true" visibility="true">
18+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
19+
<operations public="true" package="true" protected="true" private="true" static="true"/>
20+
</display>
21+
</class>
22+
<class id="3" language="java" name="main.java.com.wssia.caching.CacheStore" project="CachingPatterns"
23+
file="/CachingPatterns/src/main/java/com/wssia/caching/CacheStore.java" binary="false" corner="BOTTOM_RIGHT">
24+
<position height="-1" width="-1" x="537" y="436"/>
25+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
26+
sort-features="false" accessors="true" visibility="true">
27+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
28+
<operations public="true" package="true" protected="true" private="true" static="true"/>
29+
</display>
30+
</class>
31+
<enumeration id="4" language="java" name="main.java.com.wssia.caching.CachingPolicy" project="CachingPatterns"
32+
file="/CachingPatterns/src/main/java/com/wssia/caching/CachingPolicy.java" binary="false" corner="BOTTOM_RIGHT">
33+
<position height="-1" width="-1" x="789" y="162"/>
34+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
35+
sort-features="false" accessors="true" visibility="true">
36+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
37+
<operations public="true" package="true" protected="true" private="true" static="true"/>
38+
</display>
39+
</enumeration>
40+
<class id="5" language="java" name="main.java.com.wssia.caching.DBManager" project="CachingPatterns"
41+
file="/CachingPatterns/src/main/java/com/wssia/caching/DBManager.java" binary="false" corner="BOTTOM_RIGHT">
42+
<position height="-1" width="-1" x="1137" y="134"/>
43+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
44+
sort-features="false" accessors="true" visibility="true">
45+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
46+
<operations public="true" package="true" protected="true" private="true" static="true"/>
47+
</display>
48+
</class>
49+
<class id="6" language="java" name="main.java.com.wssia.caching.LRUCache" project="CachingPatterns"
50+
file="/CachingPatterns/src/main/java/com/wssia/caching/LRUCache.java" binary="false" corner="BOTTOM_RIGHT">
51+
<position height="-1" width="-1" x="884" y="435"/>
52+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
53+
sort-features="false" accessors="true" visibility="true">
54+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
55+
<operations public="true" package="true" protected="true" private="true" static="true"/>
56+
</display>
57+
</class>
58+
<class id="7" language="java" name="main.java.com.wssia.caching.UserAccount" project="CachingPatterns"
59+
file="/CachingPatterns/src/main/java/com/wssia/caching/UserAccount.java" binary="false" corner="BOTTOM_RIGHT">
60+
<position height="-1" width="-1" x="1140" y="405"/>
61+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
62+
sort-features="false" accessors="true" visibility="true">
63+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
64+
<operations public="true" package="true" protected="true" private="true" static="true"/>
65+
</display>
66+
</class>
67+
<class id="8" language="java" name="test.java.com.wssia.caching.AppTest" project="CachingPatterns"
68+
file="/CachingPatterns/src/test/java/com/wssia/caching/AppTest.java" binary="false" corner="BOTTOM_RIGHT">
69+
<position height="-1" width="-1" x="251" y="374"/>
70+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
71+
sort-features="false" accessors="true" visibility="true">
72+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
73+
<operations public="true" package="true" protected="true" private="true" static="true"/>
74+
</display>
75+
</class>
76+
<association id="9">
77+
<end type="SOURCE" refId="2" navigable="false">
78+
<attribute id="10" name="cachingPolicy"/>
79+
<multiplicity id="11" minimum="0" maximum="1"/>
80+
</end>
81+
<end type="TARGET" refId="4" navigable="true"/>
82+
<display labels="true" multiplicity="true"/>
83+
</association>
84+
<association id="12">
85+
<end type="SOURCE" refId="8" navigable="false">
86+
<attribute id="13" name="app"/>
87+
<multiplicity id="14" minimum="0" maximum="1"/>
88+
</end>
89+
<end type="TARGET" refId="1" navigable="true"/>
90+
<display labels="true" multiplicity="true"/>
91+
</association>
92+
<association id="15">
93+
<end type="SOURCE" refId="3" navigable="false">
94+
<attribute id="16" name="cache"/>
95+
<multiplicity id="17" minimum="0" maximum="1"/>
96+
</end>
97+
<end type="TARGET" refId="6" navigable="true"/>
98+
<display labels="true" multiplicity="true"/>
99+
</association>
100+
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
101+
sort-features="false" accessors="true" visibility="true">
102+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
103+
<operations public="true" package="true" protected="true" private="true" static="true"/>
104+
</classifier-display>
105+
<association-display labels="true" multiplicity="true"/>
106+
</class-diagram>

caching/index.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
layout: pattern
3+
title: Caching
4+
folder: caching
5+
permalink: /patterns/caching/
6+
categories: Other
7+
tags:
8+
- Java
9+
---
10+
11+
**Intent:** To avoid expensive re-acquisition of resources by not releasing
12+
the resources immediately after their use. The resources retain their identity, are kept in some
13+
fast-access storage, and are re-used to avoid having to acquire them again.
14+
15+
![alt text](./etc/caching.png "Caching")
16+
17+
**Applicability:** Use the Caching pattern(s) when
18+
19+
* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
20+
21+
**Credits**
22+
23+
* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
24+
* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177)

caching/pom.xml

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0"?>
2+
<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"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>com.iluwatar</groupId>
7+
<artifactId>java-design-patterns</artifactId>
8+
<version>1.7.0</version>
9+
</parent>
10+
<artifactId>caching</artifactId>
11+
<dependencies>
12+
<dependency>
13+
<groupId>junit</groupId>
14+
<artifactId>junit</artifactId>
15+
<scope>test</scope>
16+
</dependency>
17+
<dependency>
18+
<groupId>org.mongodb</groupId>
19+
<artifactId>mongodb-driver</artifactId>
20+
<version>3.0.4</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>org.mongodb</groupId>
24+
<artifactId>mongodb-driver-core</artifactId>
25+
<version>3.0.4</version>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.mongodb</groupId>
29+
<artifactId>bson</artifactId>
30+
<version>3.0.4</version>
31+
</dependency>
32+
</dependencies>
33+
<!--
34+
Due to the use of MongoDB in the test of this pattern, TRAVIS and/or MAVEN might fail if the DB connection is
35+
not open for the JUnit test. To avoid disrupting the compilation process, the surefire plug-in was used
36+
to SKIP the running of the JUnit tests for this pattern. To ACTIVATE the running of the tests, change the
37+
skipTests (below) flag to 'false' and vice-versa.
38+
-->
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>org.apache.maven.plugins</groupId>
43+
<artifactId>maven-surefire-plugin</artifactId>
44+
<version>2.19</version>
45+
<configuration>
46+
<skipTests>false</skipTests>
47+
</configuration>
48+
</plugin>
49+
</plugins>
50+
</build>
51+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.iluwatar.caching;
2+
3+
/**
4+
*
5+
* The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
6+
* the resources immediately after their use. The resources retain their identity, are kept in some
7+
* fast-access storage, and are re-used to avoid having to acquire them again. There are three main
8+
* caching strategies/techniques in this pattern; each with their own pros and cons. They are:
9+
* <code>write-through</code> which writes data to the cache and DB in a single transaction,
10+
* <code>write-around</code> which writes data immediately into the DB instead of the cache, and
11+
* <code>write-behind</code> which writes data into the cache initially whilst the data is only
12+
* written into the DB when the cache is full. The <code>read-through</code> strategy is also
13+
* included in the mentioned three strategies -- returns data from the cache to the caller <b>if</b>
14+
* it exists <b>else</b> queries from DB and stores it into the cache for future use. These
15+
* strategies determine when the data in the cache should be written back to the backing store (i.e.
16+
* Database) and help keep both data sources synchronized/up-to-date. This pattern can improve
17+
* performance and also helps to maintain consistency between data held in the cache and the data in
18+
* the underlying data store.
19+
* <p>
20+
* In this example, the user account ({@link UserAccount}) entity is used as the underlying
21+
* application data. The cache itself is implemented as an internal (Java) data structure. It adopts
22+
* a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three
23+
* strategies are individually tested. The testing of the cache is restricted towards saving and
24+
* querying of user accounts from the underlying data store ( {@link DBManager}). The main class (
25+
* {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and
26+
* whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager
27+
* ({@link AppManager}) handles the transaction of data to-and-from the underlying data store
28+
* (depending on the preferred caching policy/strategy).
29+
*
30+
* <i>App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager</i>
31+
* </p>
32+
*
33+
* @see CacheStore
34+
* @See LRUCache
35+
* @see CachingPolicy
36+
*
37+
*/
38+
public class App {
39+
40+
/**
41+
* Program entry point
42+
*
43+
* @param args command line args
44+
*/
45+
public static void main(String[] args) {
46+
AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
47+
// and the App class to avoid Maven compilation errors. Set flag to
48+
// true to run the tests with MongoDB (provided that MongoDB is
49+
// installed and socket connection is open).
50+
AppManager.initCacheCapacity(3);
51+
App app = new App();
52+
app.useReadAndWriteThroughStrategy();
53+
app.useReadThroughAndWriteAroundStrategy();
54+
app.useReadThroughAndWriteBehindStrategy();
55+
}
56+
57+
/**
58+
* Read-through and write-through
59+
*/
60+
public void useReadAndWriteThroughStrategy() {
61+
System.out.println("# CachingPolicy.THROUGH");
62+
AppManager.initCachingPolicy(CachingPolicy.THROUGH);
63+
64+
UserAccount userAccount1 = new UserAccount("001", "John", "He is a boy.");
65+
66+
AppManager.save(userAccount1);
67+
System.out.println(AppManager.printCacheContent());
68+
userAccount1 = AppManager.find("001");
69+
userAccount1 = AppManager.find("001");
70+
}
71+
72+
/**
73+
* Read-through and write-around
74+
*/
75+
public void useReadThroughAndWriteAroundStrategy() {
76+
System.out.println("# CachingPolicy.AROUND");
77+
AppManager.initCachingPolicy(CachingPolicy.AROUND);
78+
79+
UserAccount userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
80+
81+
AppManager.save(userAccount2);
82+
System.out.println(AppManager.printCacheContent());
83+
userAccount2 = AppManager.find("002");
84+
System.out.println(AppManager.printCacheContent());
85+
userAccount2 = AppManager.find("002");
86+
userAccount2.setUserName("Jane G.");
87+
AppManager.save(userAccount2);
88+
System.out.println(AppManager.printCacheContent());
89+
userAccount2 = AppManager.find("002");
90+
System.out.println(AppManager.printCacheContent());
91+
userAccount2 = AppManager.find("002");
92+
}
93+
94+
/**
95+
* Read-through and write-behind
96+
*/
97+
public void useReadThroughAndWriteBehindStrategy() {
98+
System.out.println("# CachingPolicy.BEHIND");
99+
AppManager.initCachingPolicy(CachingPolicy.BEHIND);
100+
101+
UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
102+
UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
103+
UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
104+
105+
AppManager.save(userAccount3);
106+
AppManager.save(userAccount4);
107+
AppManager.save(userAccount5);
108+
System.out.println(AppManager.printCacheContent());
109+
userAccount3 = AppManager.find("003");
110+
System.out.println(AppManager.printCacheContent());
111+
UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
112+
AppManager.save(userAccount6);
113+
System.out.println(AppManager.printCacheContent());
114+
userAccount4 = AppManager.find("004");
115+
System.out.println(AppManager.printCacheContent());
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.iluwatar.caching;
2+
3+
import java.text.ParseException;
4+
5+
/**
6+
*
7+
* AppManager helps to bridge the gap in communication between the main class and the application's
8+
* back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
9+
* also initialized here. Before the cache can be used, the size of the cache has to be set.
10+
* Depending on the chosen caching policy, AppManager will call the appropriate function in the
11+
* CacheStore class.
12+
*
13+
*/
14+
public class AppManager {
15+
16+
private static CachingPolicy cachingPolicy;
17+
18+
/**
19+
*
20+
* Developer/Tester is able to choose whether the application should use MongoDB as its underlying
21+
* data storage or a simple Java data structure to (temporarily) store the data/objects during
22+
* runtime.
23+
*/
24+
public static void initDB(boolean useMongoDB) {
25+
if (useMongoDB) {
26+
try {
27+
DBManager.connect();
28+
} catch (ParseException e) {
29+
e.printStackTrace();
30+
}
31+
} else {
32+
DBManager.createVirtualDB();
33+
}
34+
}
35+
36+
public static void initCachingPolicy(CachingPolicy policy) {
37+
cachingPolicy = policy;
38+
if (cachingPolicy == CachingPolicy.BEHIND) {
39+
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
40+
@Override
41+
public void run() {
42+
CacheStore.flushCache();
43+
}
44+
}));
45+
}
46+
CacheStore.clearCache();
47+
}
48+
49+
public static void initCacheCapacity(int capacity) {
50+
CacheStore.initCapacity(capacity);
51+
}
52+
53+
public static UserAccount find(String userID) {
54+
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
55+
return CacheStore.readThrough(userID);
56+
} else if (cachingPolicy == CachingPolicy.BEHIND) {
57+
return CacheStore.readThroughWithWriteBackPolicy(userID);
58+
}
59+
return null;
60+
}
61+
62+
public static void save(UserAccount userAccount) {
63+
if (cachingPolicy == CachingPolicy.THROUGH) {
64+
CacheStore.writeThrough(userAccount);
65+
} else if (cachingPolicy == CachingPolicy.AROUND) {
66+
CacheStore.writeAround(userAccount);
67+
} else if (cachingPolicy == CachingPolicy.BEHIND) {
68+
CacheStore.writeBehind(userAccount);
69+
}
70+
}
71+
72+
public static String printCacheContent() {
73+
return CacheStore.print();
74+
}
75+
}

0 commit comments

Comments
 (0)