@@ -10,20 +10,326 @@ tags:
1010---
1111
1212## Intent
13- To avoid expensive re-acquisition of resources by not releasing
14- the resources immediately after their use. The resources retain their identity, are kept in some
15- fast-access storage, and are re-used to avoid having to acquire them again.
13+
14+ The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately
15+ after use. The resources retain their identity, are kept in some fast-access storage, and are
16+ re-used to avoid having to acquire them again.
17+
18+ ## Explanation
19+
20+ Real world example
21+
22+ > A team is working on a website that provides new homes for abandoned cats. People can post their
23+ > cats on the website after registering, but all the new posts require approval from one of the
24+ > site moderators. The user accounts of the site moderators contain a specific flag and the data
25+ > is stored in a MongoDB database. Checking for the moderator flag each time a post is viewed
26+ > becomes expensive and it's a good idea to utilize caching here.
27+
28+ In plain words
29+
30+ > Caching pattern keeps frequently needed data in fast-access storage to improve performance.
31+
32+ Wikipedia says:
33+
34+ > In computing, a cache is a hardware or software component that stores data so that future
35+ > requests for that data can be served faster; the data stored in a cache might be the result of
36+ > an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested
37+ > data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by
38+ > reading data from the cache, which is faster than recomputing a result or reading from a slower
39+ > data store; thus, the more requests that can be served from the cache, the faster the system
40+ > performs.
41+
42+ ** Programmatic Example**
43+
44+ Let's first look at the data layer of our application. The interesting classes are ` UserAccount `
45+ which is a simple Java object containing the user account details, and ` DbManager ` which handles
46+ reading and writing of these objects to/from MongoDB database.
47+
48+ ``` java
49+ @Setter
50+ @Getter
51+ @AllArgsConstructor
52+ @ToString
53+ public class UserAccount {
54+ private String userId;
55+ private String userName;
56+ private String additionalInfo;
57+ }
58+
59+ @Slf4j
60+ public final class DbManager {
61+
62+ private static MongoClient mongoClient;
63+ private static MongoDatabase db;
64+
65+ private DbManager () { /* ...*/ }
66+
67+ public static void createVirtualDb () { /* ...*/ }
68+
69+ public static void connect () throws ParseException { /* ...*/ }
70+
71+ public static UserAccount readFromDb (String userId ) { /* ...*/ }
72+
73+ public static void writeToDb (UserAccount userAccount ) { /* ...*/ }
74+
75+ public static void updateDb (UserAccount userAccount ) { /* ...*/ }
76+
77+ public static void upsertDb (UserAccount userAccount ) { /* ...*/ }
78+ }
79+ ```
80+
81+ In the example, we are demonstrating various different caching policies
82+
83+ * Write-through writes data to the cache and DB in a single transaction
84+ * Write-around writes data immediately into the DB instead of the cache
85+ * Write-behind writes data into the cache initially whilst the data is only written into the DB
86+ when the cache is full
87+ * Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to
88+ the application itself
89+ * Read-through strategy is also included in the aforementioned strategies and it returns data from
90+ the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for
91+ future use.
92+
93+ The cache implementation in ` LruCache ` is a hash table accompanied by a doubly
94+ linked-list. The linked-list helps in capturing and maintaining the LRU data in the cache. When
95+ data is queried (from the cache), added (to the cache), or updated, the data is moved to the front
96+ of the list to depict itself as the most-recently-used data. The LRU data is always at the end of
97+ the list.
98+
99+ ``` java
100+ @Slf4j
101+ public class LruCache {
102+
103+ static class Node {
104+ String userId;
105+ UserAccount userAccount;
106+ Node previous;
107+ Node next;
108+
109+ public Node (String userId , UserAccount userAccount ) {
110+ this . userId = userId;
111+ this . userAccount = userAccount;
112+ }
113+ }
114+
115+ /* ... omitted details ... */
116+
117+ public LruCache (int capacity ) {
118+ this . capacity = capacity;
119+ }
120+
121+ public UserAccount get (String userId ) {
122+ if (cache. containsKey(userId)) {
123+ var node = cache. get(userId);
124+ remove(node);
125+ setHead(node);
126+ return node. userAccount;
127+ }
128+ return null ;
129+ }
130+
131+ public void set (String userId , UserAccount userAccount ) {
132+ if (cache. containsKey(userId)) {
133+ var old = cache. get(userId);
134+ old. userAccount = userAccount;
135+ remove(old);
136+ setHead(old);
137+ } else {
138+ var newNode = new Node (userId, userAccount);
139+ if (cache. size() >= capacity) {
140+ LOGGER . info(" # Cache is FULL! Removing {} from cache..." , end. userId);
141+ cache. remove(end. userId); // remove LRU data from cache.
142+ remove(end);
143+ setHead(newNode);
144+ } else {
145+ setHead(newNode);
146+ }
147+ cache. put(userId, newNode);
148+ }
149+ }
150+
151+ public boolean contains (String userId ) {
152+ return cache. containsKey(userId);
153+ }
154+
155+ public void remove (Node node ) { /* ... */ }
156+ public void setHead (Node node ) { /* ... */ }
157+ public void invalidate (String userId ) { /* ... */ }
158+ public boolean isFull () { /* ... */ }
159+ public UserAccount getLruData () { /* ... */ }
160+ public void clear () { /* ... */ }
161+ public List<UserAccount > getCacheDataInListForm () { /* ... */ }
162+ public void setCapacity (int newCapacity ) { /* ... */ }
163+ }
164+ ```
165+
166+ The next layer we are going to look at is ` CacheStore ` which implements the different caching
167+ strategies.
168+
169+ ``` java
170+ @Slf4j
171+ public class CacheStore {
172+
173+ private static LruCache cache;
174+
175+ /* ... details omitted ... */
176+
177+ public static UserAccount readThrough (String userId ) {
178+ if (cache. contains(userId)) {
179+ LOGGER . info(" # Cache Hit!" );
180+ return cache. get(userId);
181+ }
182+ LOGGER . info(" # Cache Miss!" );
183+ UserAccount userAccount = DbManager . readFromDb(userId);
184+ cache. set(userId, userAccount);
185+ return userAccount;
186+ }
187+
188+ public static void writeThrough (UserAccount userAccount ) {
189+ if (cache. contains(userAccount. getUserId())) {
190+ DbManager . updateDb(userAccount);
191+ } else {
192+ DbManager . writeToDb(userAccount);
193+ }
194+ cache. set(userAccount. getUserId(), userAccount);
195+ }
196+
197+ public static void clearCache () {
198+ if (cache != null ) {
199+ cache. clear();
200+ }
201+ }
202+
203+ public static void flushCache () {
204+ LOGGER . info(" # flushCache..." );
205+ Optional . ofNullable(cache)
206+ .map(LruCache :: getCacheDataInListForm)
207+ .orElse(List . of())
208+ .forEach(DbManager :: updateDb);
209+ }
210+
211+ /* ... omitted the implementation of other caching strategies ... */
212+
213+ }
214+ ```
215+
216+ ` AppManager ` helps to bridge the gap in communication between the main class and the application's
217+ back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
218+ also initialized here. Before the cache can be used, the size of the cache has to be set. Depending
219+ on the chosen caching policy, ` AppManager ` will call the appropriate function in the ` CacheStore `
220+ class.
221+
222+ ``` java
223+ @Slf4j
224+ public final class AppManager {
225+
226+ private static CachingPolicy cachingPolicy;
227+
228+ private AppManager () {
229+ }
230+
231+ public static void initDb (boolean useMongoDb ) { /* ... */ }
232+
233+ public static void initCachingPolicy (CachingPolicy policy ) { /* ... */ }
234+
235+ public static void initCacheCapacity (int capacity ) { /* ... */ }
236+
237+ public static UserAccount find (String userId ) {
238+ if (cachingPolicy == CachingPolicy . THROUGH || cachingPolicy == CachingPolicy . AROUND ) {
239+ return CacheStore . readThrough(userId);
240+ } else if (cachingPolicy == CachingPolicy . BEHIND ) {
241+ return CacheStore . readThroughWithWriteBackPolicy(userId);
242+ } else if (cachingPolicy == CachingPolicy . ASIDE ) {
243+ return findAside(userId);
244+ }
245+ return null ;
246+ }
247+
248+ public static void save (UserAccount userAccount ) {
249+ if (cachingPolicy == CachingPolicy . THROUGH ) {
250+ CacheStore . writeThrough(userAccount);
251+ } else if (cachingPolicy == CachingPolicy . AROUND ) {
252+ CacheStore . writeAround(userAccount);
253+ } else if (cachingPolicy == CachingPolicy . BEHIND ) {
254+ CacheStore . writeBehind(userAccount);
255+ } else if (cachingPolicy == CachingPolicy . ASIDE ) {
256+ saveAside(userAccount);
257+ }
258+ }
259+
260+ public static String printCacheContent () {
261+ return CacheStore . print();
262+ }
263+
264+ /* ... details omitted ... */
265+ }
266+ ```
267+
268+ Here is what we do in the main class of the application.
269+
270+ ``` java
271+ @Slf4j
272+ public class App {
273+
274+ public static void main (String [] args ) {
275+ AppManager . initDb(false );
276+ AppManager . initCacheCapacity(3 );
277+ var app = new App ();
278+ app. useReadAndWriteThroughStrategy();
279+ app. useReadThroughAndWriteAroundStrategy();
280+ app. useReadThroughAndWriteBehindStrategy();
281+ app. useCacheAsideStategy();
282+ }
283+
284+ public void useReadAndWriteThroughStrategy () {
285+ LOGGER . info(" # CachingPolicy.THROUGH" );
286+ AppManager . initCachingPolicy(CachingPolicy . THROUGH );
287+ var userAccount1 = new UserAccount (" 001" , " John" , " He is a boy." );
288+ AppManager . save(userAccount1);
289+ LOGGER . info(AppManager . printCacheContent());
290+ AppManager . find(" 001" );
291+ AppManager . find(" 001" );
292+ }
293+
294+ public void useReadThroughAndWriteAroundStrategy () { /* ... */ }
295+
296+ public void useReadThroughAndWriteBehindStrategy () { /* ... */ }
297+
298+ public void useCacheAsideStategy () { /* ... */ }
299+ }
300+ ```
301+
302+ Finally, here is some of the console output from the program.
303+
304+ ```
305+ 12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
306+ 12:32:53.900 [main] INFO com.iluwatar.caching.App -
307+ --CACHE CONTENT--
308+ UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
309+ ----
310+ ```
16311
17312## Class diagram
313+
18314![ alt text] ( ./etc/caching.png " Caching ")
19315
20316## Applicability
317+
21318Use the Caching pattern(s) when
22319
23- * Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
320+ * Repetitious acquisition, initialization, and release of the same resource cause unnecessary
321+ performance overhead.
322+
323+ ## Related patterns
324+
325+ * [ Proxy] ( https://java-design-patterns.com/patterns/proxy/ )
24326
25327## Credits
26328
27329* [ Write-through, write-around, write-back: Cache explained] ( http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained )
28330* [ Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching] ( https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177 )
29331* [ Cache-Aside pattern] ( https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside )
332+ * [ Java EE 8 High Performance: Master techniques such as memory optimization, caching, concurrency, and multithreading to achieve maximum performance from your enterprise applications] ( https://www.amazon.com/gp/product/178847306X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=178847306X&linkId=e948720055599f248cdac47da9125ff4 )
333+ * [ Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond] ( https://www.amazon.com/gp/product/1492056111/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1492056111&linkId=7e553581559b9ec04221259e52004b08 )
334+ * [ Effective Java] ( https://www.amazon.com/gp/product/B078H61SCH/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B078H61SCH&linkId=f06607a0b48c76541ef19c5b8b9e7882 )
335+ * [ Java Performance: The Definitive Guide: Getting the Most Out of Your Code] ( https://www.amazon.com/gp/product/1449358454/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1449358454&linkId=475c18363e350630cc0b39ab681b2687 )
0 commit comments