Compare commits

..

51 Commits

Author SHA1 Message Date
e05595194d Update image paths for localizations 2021-11-01 21:41:20 +02:00
f4d67c77f7 update circuit breaker image paths 2021-10-31 20:08:01 +02:00
4d54781f79 update image paths 2021-10-31 20:00:22 +02:00
fd915161ee Add index.md for Chinese translation 2021-10-31 18:43:28 +02:00
097ddb385f Update pattern index.md 2021-10-12 20:52:19 +03:00
e4ab8c8066 add missing png 2021-10-12 15:18:54 +03:00
c3ef3dbe8c fix frontmatter 2021-10-12 14:30:49 +03:00
c6c4e6ebb6 Merge branch 'master' into vuepress
# Conflicts:
#	README.md
#	command/README.md
2021-10-12 14:05:22 +03:00
801be2520b Merge branch 'master' of https://github.com/iluwatar/java-design-patterns 2021-10-12 13:59:21 +03:00
d247b6ed69 docs: add VxDxK as a contributor for translation (#1848)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-11 21:45:21 +03:00
0a73ead12d translation: Add Russian translation (#1846)
* Add ru url to README.md

* Create russian README.md
2021-10-11 21:43:58 +03:00
ce4a6df5c5 Merge branch 'master' of https://github.com/iluwatar/java-design-patterns 2021-10-09 09:34:47 +03:00
ddb9b14eed docs: add muklasr as a contributor for translation (#1845)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-08 20:47:25 +03:00
0255111b4e translation: Add Indonesian translation (#1841)
* Create README.md in id

* Add README.md in id url
2021-10-08 20:45:59 +03:00
9513d2be58 docs: add Conhan93 as a contributor for doc (#1844)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-08 20:36:02 +03:00
119abf3ee4 Doc: Corrected a few spelling mistakes (#1840) 2021-10-08 20:34:47 +03:00
95699c5de3 Merge branch 'issue-1694' of https://github.com/VictorZZZZ/java-design-patterns 2021-10-08 20:17:49 +03:00
c459b92933 Fixes according PR comments. Mainly Readme edits. 2021-10-06 15:34:04 +03:00
87cc4df14b docs: Add frascu as a Contributor for Code (#1835)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-05 10:07:07 +05:30
57f9c2e968 task: Update Lombok to version 1.18.20 (#1828)
Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-10-05 10:05:20 +05:30
42eb7950ae task: Fix broken links (#1817)
* Fix some broken links

* Remove extra space

* Update filename

* Fix some links in localization folders

* Fix link

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-09-29 00:13:31 +05:30
be72a96cd6 docs: add tan31989 as a contributor for code (#1820)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-09-29 00:11:14 +05:30
be25c0b433 bug-fix: Use Junit5 in the serverless module tests (#1794)
* #1667: Fixing the serverless tests to use Junit5 and also modifying other classes to remove the deprecated initMock() method

* #1667: Fixing the sonar code smells

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-09-29 00:09:19 +05:30
be59e50205 doc: Fix Typos in French local doc (#1818)
Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-09-28 21:49:30 +05:30
223b779c83 Work on index page 2021-09-12 20:54:19 +03:00
9472f10e18 Work on patterns index page 2021-09-12 20:09:17 +03:00
b6b2079a10 Update frontmatters 2021-09-12 13:27:23 +03:00
81f9291e76 Fix link 2021-09-11 19:11:30 +03:00
58c0008b75 Fix some links in localization folders 2021-09-11 19:09:39 +03:00
bc7040566e Update filename 2021-09-11 18:58:50 +03:00
bb49e3c4e9 Remove extra space 2021-09-11 17:55:04 +03:00
842b400fee Fix some broken links 2021-09-11 16:56:02 +03:00
ec90320eda docs: add mortezaadi as a contributor for code (#1816)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-09-07 21:46:23 +03:00
3126ad3106 fix: Remove unnecessary and possibly not threadsafe flag (#1811) 2021-09-07 21:43:47 +03:00
2a01563d21 Comments to start Application with mongo. 2021-09-02 15:33:06 +03:00
45af6987c1 Provided missing tests 2021-09-02 15:30:07 +03:00
6adb27ca3d Added docker-compose for mongo db. MongoDb db work fixed. 2021-09-02 15:24:08 +03:00
c0e4bf3d1d Add test 2021-09-01 10:27:46 +03:00
50755b7215 Fix Bug with mongo connection. Used "Try with resources" 2021-08-30 09:13:08 +03:00
49039843e5 Fix issues in VALIDATE phase 2021-08-27 15:29:58 +03:00
dabe4d2022 Fix sonar issues 2021-08-27 13:27:50 +03:00
aff7ef8782 Fix last errors in checkstyle. 2021-08-23 12:37:24 +03:00
c150e5f38b Fix CacheStore errors from checkstyle plugin 107 left 2021-08-22 11:12:53 +03:00
3c213fcd69 Fix 40 errors from checkstyle plugin run. 139 left)) 2021-08-17 22:05:01 +03:00
a437f5b2eb Fix Logs 2021-08-17 16:53:44 +03:00
399ad53b09 Changed database implementation. Removed static objects. 2021-08-17 16:25:59 +03:00
e2ebb59fe7 docs: add karthikbhat13 as a contributor for code (#1808)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-08-08 15:53:13 +03:00
d87e8cf10d feature: Added FanOut/FanIn Pattern (#1800)
* Added FanOut/FanIn Pattern (#8)

* #1627 adding fanout-fanin pattern

* #1627 adding class diagram image

* #1627 adding readme

* #1627 adding license

* #1627 updating relations

* #1627 interrupting the thread

* #1627 fixing sonar issues

* #1627 fixing sonar issues

* #1627 adding more info in README.md

* Added FanOut/FanIn (#9)

* #1627 adding fanout-fanin pattern

* #1627 adding class diagram image

* #1627 adding readme

* #1627 adding license

* #1627 updating relations

* #1627 interrupting the thread

* #1627 fixing sonar issues

* #1627 fixing sonar issues

* #1627 adding more info in README.md

* #1627 adding programmatic examples in README.md
2021-08-08 15:51:27 +03:00
c5a4068e84 docs: Translation for zh (#1805)
* add state and callback pattern

* add command and template-method pattern

* add iterator pattern

* add bridege and DI pattern

* fix issue #1600

* add converter,proxy,visitor pattern

* add caching,composite,delegation,dirty-flag,interpreter patterns

* add dao and producer-consumer

* add dto and provate class data pattern

* fix #1646 png path problems

* fix #1646 composite png path case problem

* add abstract document pattern and version-number pattern

* add ambassador pattern

* add acyclic-visitor and api-gateway pattern

* add abstract-factory pattern

* add active-object pattern

* add aggregator-microservices and arrange-act-assert pattern

* update async-method-invocation pattern

* add balking and business-delegate pattern

* add bytecode and circuit-break pattern

* update arrange/act/assert pattern problems

* add csch pattern

* add language code, correct pic path

* #1805 update permalink

Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
Co-authored-by: Mike <admin@xiaod.info>
Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2021-08-01 20:25:54 +05:30
d36efdbc7c TASK: Add language to yaml front matter (#1806) 2021-07-25 17:28:54 +05:30
3f654ab0c8 docs: add AndriyPyzh as a contributor for code (#1804)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-07-22 17:00:52 +03:00
311 changed files with 3355 additions and 1533 deletions

View File

@ -1568,6 +1568,69 @@
"contributions": [
"code"
]
},
{
"login": "karthikbhat13",
"name": "karthikbhat13",
"avatar_url": "https://avatars.githubusercontent.com/u/22431014?v=4",
"profile": "https://github.com/karthikbhat13",
"contributions": [
"code"
]
},
{
"login": "mortezaadi",
"name": "Morteza Adigozalpour",
"avatar_url": "https://avatars.githubusercontent.com/u/1329687?v=4",
"profile": "https://github.com/mortezaadi",
"contributions": [
"code"
]
},
{
"login": "tan31989",
"name": "Nagaraj Tantri",
"avatar_url": "https://avatars.githubusercontent.com/u/3784194?v=4",
"profile": "https://stackoverflow.com/users/308565/nagaraj-tantri",
"contributions": [
"code"
]
},
{
"login": "frascu",
"name": "Francesco Scuccimarri",
"avatar_url": "https://avatars.githubusercontent.com/u/7107651?v=4",
"profile": "http://scuccimarri.it",
"contributions": [
"code"
]
},
{
"login": "Conhan93",
"name": "Conny Hansson",
"avatar_url": "https://avatars.githubusercontent.com/u/71334757?v=4",
"profile": "https://github.com/Conhan93",
"contributions": [
"doc"
]
},
{
"login": "muklasr",
"name": "Muklas Rahmanto",
"avatar_url": "https://avatars.githubusercontent.com/u/43443753?v=4",
"profile": "http://muklasr.medium.com",
"contributions": [
"translation"
]
},
{
"login": "VxDxK",
"name": "Vadim",
"avatar_url": "https://avatars.githubusercontent.com/u/38704817?v=4",
"profile": "https://github.com/VxDxK",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 4,

View File

@ -10,12 +10,12 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-172-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-179-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
Read in different language : [**zh**](/localization/zh/README.md), [**ko**](/localization/ko/README.md), [**fr**](/localization/fr/README.md), [**tr**](/localization/tr/README.md), [**ar**](/localization/ar/README.md), [**es**](/localization/es/README.md), [**pt**](/localization/pt/README.md)
Read in different language : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md)
<br/>
@ -333,6 +333,17 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="https://github.com/marlo2222"><img src="https://avatars.githubusercontent.com/u/40809563?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marlo Henrique</b></sub></a><br /><a href="#translation-marlo2222" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/AndriyPyzh"><img src="https://avatars.githubusercontent.com/u/57706635?v=4?s=100" width="100px;" alt=""/><br /><sub><b>AndriyPyzh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=AndriyPyzh" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/karthikbhat13"><img src="https://avatars.githubusercontent.com/u/22431014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>karthikbhat13</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=karthikbhat13" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mortezaadi"><img src="https://avatars.githubusercontent.com/u/1329687?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Morteza Adigozalpour</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=mortezaadi" title="Code">💻</a></td>
<td align="center"><a href="https://stackoverflow.com/users/308565/nagaraj-tantri"><img src="https://avatars.githubusercontent.com/u/3784194?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nagaraj Tantri</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=tan31989" title="Code">💻</a></td>
<td align="center"><a href="http://scuccimarri.it"><img src="https://avatars.githubusercontent.com/u/7107651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Francesco Scuccimarri</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=frascu" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Conhan93"><img src="https://avatars.githubusercontent.com/u/71334757?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Conny Hansson</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Conhan93" title="Documentation">📖</a></td>
<td align="center"><a href="http://muklasr.medium.com"><img src="https://avatars.githubusercontent.com/u/43443753?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muklas Rahmanto</b></sub></a><br /><a href="#translation-muklasr" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/VxDxK"><img src="https://avatars.githubusercontent.com/u/38704817?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vadim</b></sub></a><br /><a href="#translation-VxDxK" title="Translation">🌍</a></td>
</tr>
</table>
<!-- markdownlint-restore -->

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Abstract Document
folder: abstract-document
permalink: /patterns/abstract-document/
categories: Structural
category: Structural
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Abstract Factory
folder: abstract-factory
permalink: /patterns/abstract-factory/
categories: Creational
category: Creational
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Active Object
folder: active-object
permalink: /patterns/active-object/
categories: Concurrency
category: Concurrency
language: en
tags:
- Performance
@ -11,7 +8,7 @@ tags:
## Intent
The active object design pattern decouples method execution from method invocation for objects that each reside in their thread of control. The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests.
The active object design pattern decouples method execution from method invocation for objects that each reside in their thread of control. The goal is to introduce concurrency, by using asynchronous method invocation, and a scheduler for handling requests.
## Explanation
@ -70,7 +67,7 @@ public abstract class ActiveCreature{
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} has started to roam and the wastelands.",name());
logger.info("{} has started to roam the wastelands.",name());
}
}
);
@ -82,7 +79,7 @@ public abstract class ActiveCreature{
}
```
We can see that any class that will extend the ActiveCreature class will have its own thread of control to execute and invocate methods.
We can see that any class that will extend the ActiveCreature class will have its own thread of control to invoke and execute methods.
For example, the Orc class:
@ -96,7 +93,7 @@ public class Orc extends ActiveCreature {
}
```
Now, we can create multiple creatures such as Orcs, tell them to eat and roam and they will execute it on their own thread of control:
Now, we can create multiple creatures such as Orcs, tell them to eat and roam, and they will execute it on their own thread of control:
```java
public static void main(String[] args) {
@ -123,4 +120,4 @@ Now, we can create multiple creatures such as Orcs, tell them to eat and roam an
## Class diagram
![alt text](./etc/active-object.urm.PNG "Active Object class diagram")
![alt text](./etc/active-object.urm.png "Active Object class diagram")

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -82,7 +82,7 @@ public abstract class ActiveCreature {
}
/**
* Roam in the wastelands.
* Roam the wastelands.
* @throws InterruptedException due to firing a new Runnable.
*/
public void roam() throws InterruptedException {

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Acyclic Visitor
folder: acyclic-visitor
permalink: /patterns/acyclic-visitor/
categories: Behavioral
category: Behavioral
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Adapter
folder: adapter
permalink: /patterns/adapter/
categories: Structural
category: Structural
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Aggregator Microservices
folder: aggregator-microservices
permalink: /patterns/aggregator-microservices/
categories: Architectural
category: Architectural
language: en
tags:
- Cloud distributed

View File

@ -48,7 +48,7 @@ class AggregatorTest {
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);
}
/**

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Ambassador
folder: ambassador
permalink: /patterns/ambassador/
categories: Structural
category: Structural
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: API Gateway
folder: api-gateway
permalink: /patterns/api-gateway/
categories: Architectural
category: Architectural
language: en
tags:
- Cloud distributed

View File

@ -48,7 +48,7 @@ class ApiGatewayTest {
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);
}
/**

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Arrange/Act/Assert
folder: arrange-act-assert
permalink: /patterns/arrange-act-assert/
categories: Idiom
category: Idiom
language: en
tags:
- Testing

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Async Method Invocation
folder: async-method-invocation
permalink: /patterns/async-method-invocation/
categories: Concurrency
category: Concurrency
language: en
tags:
- Reactive

View File

@ -68,7 +68,7 @@ class ThreadAsyncExecutorTest {
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);
}
/**

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Balking
folder: balking
permalink: /patterns/balking/
categories: Concurrency
category: Concurrency
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Bridge
folder: bridge
permalink: /patterns/bridge/
categories: Structural
category: Structural
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Builder
folder: builder
permalink: /patterns/builder/
categories: Creational
category: Creational
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Business Delegate
folder: business-delegate
permalink: /patterns/business-delegate/
categories: Structural
category: Structural
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Bytecode
folder: bytecode
permalink: /patterns/bytecode/
categories: Behavioral
category: Behavioral
language: en
tags:
- Game programming

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Caching
folder: caching
permalink: /patterns/caching/
categories: Behavioral
category: Behavioral
language: en
tags:
- Performance
@ -43,39 +40,29 @@ Wikipedia says:
**Programmatic Example**
Let's first look at the data layer of our application. The interesting classes are `UserAccount`
which is a simple Java object containing the user account details, and `DbManager` which handles
reading and writing of these objects to/from MongoDB database.
which is a simple Java object containing the user account details, and `DbManager` interface which handles
reading and writing of these objects to/from database.
```java
@Setter
@Getter
@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class UserAccount {
private String userId;
private String userName;
private String additionalInfo;
}
@Slf4j
public final class DbManager {
public interface DbManager {
private static MongoClient mongoClient;
private static MongoDatabase db;
private DbManager() { /*...*/ }
public static void createVirtualDb() { /*...*/ }
public static void connect() throws ParseException { /*...*/ }
public static UserAccount readFromDb(String userId) { /*...*/ }
public static void writeToDb(UserAccount userAccount) { /*...*/ }
public static void updateDb(UserAccount userAccount) { /*...*/ }
public static void upsertDb(UserAccount userAccount) { /*...*/ }
void connect();
void disconnect();
UserAccount readFromDb(String userId);
UserAccount writeToDb(UserAccount userAccount);
UserAccount updateDb(UserAccount userAccount);
UserAccount upsertDb(UserAccount userAccount);
}
```
@ -171,30 +158,43 @@ strategies.
@Slf4j
public class CacheStore {
private static final int CAPACITY = 3;
private static LruCache cache;
private final DbManager dbManager;
/* ... details omitted ... */
public static UserAccount readThrough(String userId) {
public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Cache Hit!");
LOGGER.info("# Found in Cache!");
return cache.get(userId);
}
LOGGER.info("# Cache Miss!");
UserAccount userAccount = DbManager.readFromDb(userId);
LOGGER.info("# Not found in cache! Go to DB!!");
UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
public static void writeThrough(UserAccount userAccount) {
public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
DbManager.updateDb(userAccount);
dbManager.updateDb(userAccount);
} else {
DbManager.writeToDb(userAccount);
dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
// Cache data has been updated -- remove older
cache.invalidate(userAccount.getUserId());
// version from cache.
} else {
dbManager.writeToDb(userAccount);
}
}
public static void clearCache() {
if (cache != null) {
cache.clear();
@ -225,34 +225,39 @@ class.
public final class AppManager {
private static CachingPolicy cachingPolicy;
private final DbManager dbManager;
private final CacheStore cacheStore;
private AppManager() {
}
public static void initDb(boolean useMongoDb) { /* ... */ }
public void initDb() { /* ... */ }
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
public static void initCacheCapacity(int capacity) { /* ... */ }
public static UserAccount find(String userId) {
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
return CacheStore.readThrough(userId);
public UserAccount find(final String userId) {
LOGGER.info("Trying to find {} in cache", userId);
if (cachingPolicy == CachingPolicy.THROUGH
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return CacheStore.readThroughWithWriteBackPolicy(userId);
return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
public static void save(UserAccount userAccount) {
public void save(final UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
CacheStore.writeThrough(userAccount);
cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
CacheStore.writeAround(userAccount);
cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
CacheStore.writeBehind(userAccount);
cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
@ -272,24 +277,35 @@ Here is what we do in the main class of the application.
@Slf4j
public class App {
public static void main(String[] args) {
AppManager.initDb(false);
AppManager.initCacheCapacity(3);
var app = new App();
public static void main(final String[] args) {
boolean isDbMongo = isDbMongo(args);
if(isDbMongo){
LOGGER.info("Using the Mongo database engine to run the application.");
} else {
LOGGER.info("Using the 'in Memory' database to run the application.");
}
App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
AppManager.initCachingPolicy(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");
appManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
@ -300,16 +316,6 @@ public class App {
}
```
Finally, here is some of the console output from the program.
```
12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
12:32:53.900 [main] INFO com.iluwatar.caching.App -
--CACHE CONTENT--
UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
----
```
## Class diagram
![alt text](./etc/caching.png "Caching")

View File

@ -0,0 +1,11 @@
version: '3.7'
services:
mongodb_container:
image: mongo:latest
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: rootpassword
ports:
- 27017:27017
volumes:
- ./mongo-data/:/data/db

View File

@ -39,19 +39,21 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.12.1</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</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>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
<!--

View File

@ -1,58 +1,62 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
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 maintain
* consistency between data held in the cache and the data in the underlying data store.
* 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>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>
* <i>{@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy -->
* DBManager} </i>
* </p>
*
* <p>
* There are 2 ways to launch the application.
* - to use "in Memory" database.
* - to use the MongoDb as a database
*
* To run the application with "in Memory" database, just launch it without parameters
* Example: 'java -jar app.jar'
*
* To run the application with MongoDb you need to be installed the MongoDb
* in your system, or to launch it in the docker container.
* You may launch docker container from the root of current module with command:
* 'docker-compose up'
* Then you can start the application with parameter --mongo
* Example: 'java -jar app.jar --mongo'
* </p>
*
* @see CacheStore
@ -61,23 +65,67 @@ import lombok.extern.slf4j.Slf4j;
*/
@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(String[] args) {
AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
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).
AppManager.initCacheCapacity(3);
var app = new App();
boolean isDbMongo = isDbMongo(args);
if (isDbMongo) {
LOGGER.info("Using the Mongo database engine to run the application.");
} else {
LOGGER.info("Using the 'in Memory' database to run the application.");
}
App app = new App(isDbMongo);
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;
}
/**
@ -85,14 +133,14 @@ public class App {
*/
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
AppManager.initCachingPolicy(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");
appManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
/**
@ -100,21 +148,21 @@ public class App {
*/
public void useReadThroughAndWriteAroundStrategy() {
LOGGER.info("# CachingPolicy.AROUND");
AppManager.initCachingPolicy(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");
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");
appManager.save(userAccount2);
LOGGER.info(appManager.printCacheContent());
appManager.find("002");
LOGGER.info(appManager.printCacheContent());
appManager.find("002");
}
/**
@ -122,23 +170,31 @@ public class App {
*/
public void useReadThroughAndWriteBehindStrategy() {
LOGGER.info("# CachingPolicy.BEHIND");
AppManager.initCachingPolicy(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.");
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());
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());
}
/**
@ -146,20 +202,26 @@ public class App {
*/
public void useCacheAsideStategy() {
LOGGER.info("# CachingPolicy.ASIDE");
AppManager.initCachingPolicy(CachingPolicy.ASIDE);
LOGGER.info(AppManager.printCacheContent());
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);
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());
LOGGER.info(appManager.printCacheContent());
appManager.find("003");
LOGGER.info(appManager.printCacheContent());
appManager.find("004");
LOGGER.info(appManager.printCacheContent());
}
}

View File

@ -23,65 +23,80 @@
package com.iluwatar.caching;
import java.text.ParseException;
import com.iluwatar.caching.database.DbManager;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
/**
* 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.
* 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.
*/
@Slf4j
public final class AppManager {
public class AppManager {
/**
* Caching Policy.
*/
private CachingPolicy cachingPolicy;
/**
* Database Manager.
*/
private final DbManager dbManager;
/**
* Cache Store.
*/
private final CacheStore cacheStore;
private static CachingPolicy cachingPolicy;
private AppManager() {
/**
* Constructor.
*
* @param newDbManager database manager
*/
public AppManager(final DbManager newDbManager) {
this.dbManager = newDbManager;
this.cacheStore = new CacheStore(newDbManager);
}
/**
* 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.
* 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) {
LOGGER.error("Error connecting to MongoDB", e);
}
} else {
DbManager.createVirtualDb();
}
public void initDb() {
dbManager.connect();
}
/**
* Initialize caching policy.
*
* @param policy is a {@link CachingPolicy}
*/
public static void initCachingPolicy(CachingPolicy policy) {
public void initCachingPolicy(final CachingPolicy policy) {
cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) {
Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache));
Runtime.getRuntime().addShutdownHook(new Thread(cacheStore::flushCache));
}
CacheStore.clearCache();
}
public static void initCacheCapacity(int capacity) {
CacheStore.initCapacity(capacity);
cacheStore.clearCache();
}
/**
* Find user account.
*
* @param userId String
* @return {@link UserAccount}
*/
public static UserAccount find(String userId) {
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
return CacheStore.readThrough(userId);
public UserAccount find(final String userId) {
LOGGER.info("Trying to find {} in cache", userId);
if (cachingPolicy == CachingPolicy.THROUGH
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return CacheStore.readThroughWithWriteBackPolicy(userId);
return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
@ -90,41 +105,55 @@ public final class AppManager {
/**
* Save user account.
*
* @param userAccount {@link UserAccount}
*/
public static void save(UserAccount userAccount) {
public void save(final UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
CacheStore.writeThrough(userAccount);
cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
CacheStore.writeAround(userAccount);
cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
CacheStore.writeBehind(userAccount);
cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
}
public static String printCacheContent() {
return CacheStore.print();
/**
* Returns String.
*
* @return String
*/
public String printCacheContent() {
return cacheStore.print();
}
/**
* Cache-Aside save user account helper.
*
* @param userAccount {@link UserAccount}
*/
private static void saveAside(UserAccount userAccount) {
DbManager.updateDb(userAccount);
CacheStore.invalidate(userAccount.getUserId());
private void saveAside(final UserAccount userAccount) {
dbManager.updateDb(userAccount);
cacheStore.invalidate(userAccount.getUserId());
}
/**
* Cache-Aside find user account helper.
*
* @param userId String
* @return {@link UserAccount}
*/
private static UserAccount findAside(String userId) {
return Optional.ofNullable(CacheStore.get(userId))
.or(() -> {
Optional<UserAccount> userAccount = Optional.ofNullable(DbManager.readFromDb(userId));
userAccount.ifPresent(account -> CacheStore.set(userId, account));
return userAccount;
})
.orElse(null);
private UserAccount findAside(final String userId) {
return Optional.ofNullable(cacheStore.get(userId))
.or(() -> {
Optional<UserAccount> userAccount =
Optional.ofNullable(dbManager.readFromDb(userId));
userAccount.ifPresent(account -> cacheStore.set(userId, account));
return userAccount;
})
.orElse(null);
}
}

View File

@ -23,9 +23,11 @@
package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
@ -33,16 +35,34 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class CacheStore {
/**
* Cache capacity.
*/
private static final int CAPACITY = 3;
private static LruCache cache;
/**
* Lru cache see {@link LruCache}.
*/
private LruCache cache;
/**
* DbManager.
*/
private final DbManager dbManager;
private CacheStore() {
/**
* Cache Store.
* @param dataBaseManager {@link DbManager}
*/
public CacheStore(final DbManager dataBaseManager) {
this.dbManager = dataBaseManager;
initCapacity(CAPACITY);
}
/**
* Init cache capacity.
* @param capacity int
*/
public static void initCapacity(int capacity) {
public void initCapacity(final int capacity) {
if (cache == null) {
cache = new LruCache(capacity);
} else {
@ -52,57 +72,64 @@ public class CacheStore {
/**
* Get user account using read-through cache.
* @param userId {@link String}
* @return {@link UserAccount}
*/
public static UserAccount readThrough(String userId) {
public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Cache Hit!");
LOGGER.info("# Found in Cache!");
return cache.get(userId);
}
LOGGER.info("# Cache Miss!");
UserAccount userAccount = DbManager.readFromDb(userId);
LOGGER.info("# Not found in cache! Go to DB!!");
UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
/**
* Get user account using write-through cache.
* @param userAccount {@link UserAccount}
*/
public static void writeThrough(UserAccount userAccount) {
public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
DbManager.updateDb(userAccount);
dbManager.updateDb(userAccount);
} else {
DbManager.writeToDb(userAccount);
dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
/**
* Get user account using write-around cache.
* @param userAccount {@link UserAccount}
*/
public static void writeAround(UserAccount userAccount) {
public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
DbManager.updateDb(userAccount);
cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
dbManager.updateDb(userAccount);
// Cache data has been updated -- remove older
cache.invalidate(userAccount.getUserId());
// version from cache.
} else {
DbManager.writeToDb(userAccount);
dbManager.writeToDb(userAccount);
}
}
/**
* Get user account using read-through cache with write-back policy.
* @param userId {@link String}
* @return {@link UserAccount}
*/
public static UserAccount readThroughWithWriteBackPolicy(String userId) {
public UserAccount readThroughWithWriteBackPolicy(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Cache Hit!");
LOGGER.info("# Found in cache!");
return cache.get(userId);
}
LOGGER.info("# Cache Miss!");
UserAccount userAccount = DbManager.readFromDb(userId);
LOGGER.info("# Not found in Cache!");
UserAccount userAccount = dbManager.readFromDb(userId);
if (cache.isFull()) {
LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
UserAccount toBeWrittenToDb = cache.getLruData();
DbManager.upsertDb(toBeWrittenToDb);
dbManager.upsertDb(toBeWrittenToDb);
}
cache.set(userId, userAccount);
return userAccount;
@ -110,12 +137,13 @@ public class CacheStore {
/**
* Set user account.
* @param userAccount {@link UserAccount}
*/
public static void writeBehind(UserAccount userAccount) {
public void writeBehind(final UserAccount userAccount) {
if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
UserAccount toBeWrittenToDb = cache.getLruData();
DbManager.upsertDb(toBeWrittenToDb);
dbManager.upsertDb(toBeWrittenToDb);
}
cache.set(userAccount.getUserId(), userAccount);
}
@ -123,7 +151,7 @@ public class CacheStore {
/**
* Clears cache.
*/
public static void clearCache() {
public void clearCache() {
if (cache != null) {
cache.clear();
}
@ -132,44 +160,51 @@ public class CacheStore {
/**
* Writes remaining content in the cache into the DB.
*/
public static void flushCache() {
public void flushCache() {
LOGGER.info("# flushCache...");
Optional.ofNullable(cache)
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.forEach(DbManager::updateDb);
.forEach(dbManager::updateDb);
dbManager.disconnect();
}
/**
* Print user accounts.
* @return {@link String}
*/
public static String print() {
public String print() {
return Optional.ofNullable(cache)
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.stream()
.map(userAccount -> userAccount.toString() + "\n")
.collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----\n"));
.collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----"));
}
/**
* Delegate to backing cache store.
* @param userId {@link String}
* @return {@link UserAccount}
*/
public static UserAccount get(String userId) {
public UserAccount get(final String userId) {
return cache.get(userId);
}
/**
* Delegate to backing cache store.
* @param userId {@link String}
* @param userAccount {@link UserAccount}
*/
public static void set(String userId, UserAccount userAccount) {
public void set(final String userId, final UserAccount userAccount) {
cache.set(userId, userAccount);
}
/**
* Delegate to backing cache store.
* @param userId {@link String}
*/
public static void invalidate(String userId) {
public void invalidate(final String userId) {
cache.invalidate(userId);
}
}

View File

@ -32,10 +32,25 @@ import lombok.Getter;
@AllArgsConstructor
@Getter
public enum CachingPolicy {
/**
* Through.
*/
THROUGH("through"),
/**
* AROUND.
*/
AROUND("around"),
/**
* BEHIND.
*/
BEHIND("behind"),
/**
* ASIDE.
*/
ASIDE("aside");
/**
* Policy value.
*/
private final String policy;
}

View File

@ -1,171 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.caching;
import com.iluwatar.caching.constants.CachingConstants;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
/**
* <p>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.</p>
*
* <p>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()).</p>
*/
@Slf4j
public final class DbManager {
private static MongoClient mongoClient;
private static MongoDatabase db;
private static boolean useMongoDB;
private static Map<String, UserAccount> virtualDB;
private DbManager() {
}
/**
* Create DB.
*/
public static void createVirtualDb() {
useMongoDB = false;
virtualDB = new HashMap<>();
}
/**
* Connect to DB.
*/
public static void connect() throws ParseException {
useMongoDB = true;
mongoClient = new MongoClient();
db = mongoClient.getDatabase("test");
}
/**
* Read user account from DB.
*/
public static UserAccount readFromDb(String userId) {
if (!useMongoDB) {
if (virtualDB.containsKey(userId)) {
return virtualDB.get(userId);
}
return null;
}
if (db == null) {
try {
connect();
} catch (ParseException e) {
LOGGER.error("Error connecting to MongoDB", e);
}
}
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
.find(new Document(CachingConstants.USER_ID, userId));
if (iterable == null) {
return null;
}
Document doc = iterable.first();
String userName = doc.getString(CachingConstants.USER_NAME);
String appInfo = doc.getString(CachingConstants.ADD_INFO);
return new UserAccount(userId, userName, appInfo);
}
/**
* Write user account to DB.
*/
public static void writeToDb(UserAccount userAccount) {
if (!useMongoDB) {
virtualDB.put(userAccount.getUserId(), userAccount);
return;
}
if (db == null) {
try {
connect();
} catch (ParseException e) {
LOGGER.error("Error connecting to MongoDB", e);
}
}
db.getCollection(CachingConstants.USER_ACCOUNT).insertOne(
new Document(CachingConstants.USER_ID, userAccount.getUserId())
.append(CachingConstants.USER_NAME, userAccount.getUserName())
.append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo())
);
}
/**
* Update DB.
*/
public static void updateDb(UserAccount userAccount) {
if (!useMongoDB) {
virtualDB.put(userAccount.getUserId(), userAccount);
return;
}
if (db == null) {
try {
connect();
} catch (ParseException e) {
LOGGER.error("Error connecting to MongoDB", e);
}
}
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
new Document(CachingConstants.USER_ID, userAccount.getUserId()),
new Document("$set", new Document(CachingConstants.USER_NAME, userAccount.getUserName())
.append(CachingConstants.ADD_INFO, 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 (db == null) {
try {
connect();
} catch (ParseException e) {
LOGGER.error("Error connecting to MongoDB", e);
}
}
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
new Document(CachingConstants.USER_ID, userAccount.getUserId()),
new Document("$set",
new Document(CachingConstants.USER_ID, userAccount.getUserId())
.append(CachingConstants.USER_NAME, userAccount.getUserName())
.append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo())
),
new UpdateOptions().upsert(true)
);
}
}

View File

@ -29,41 +29,83 @@ import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
/**
* 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.
* 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.
*/
@Slf4j
public class LruCache {
/**
* Static class Node.
*/
static class Node {
String userId;
UserAccount userAccount;
Node previous;
Node next;
/**
* user id.
*/
private final String userId;
/**
* User Account.
*/
private UserAccount userAccount;
/**
* previous.
*/
private Node previous;
/**
* next.
*/
private Node next;
public Node(String userId, UserAccount userAccount) {
this.userId = userId;
this.userAccount = userAccount;
/**
* Node definition.
*
* @param id String
* @param account {@link UserAccount}
*/
Node(final String id, final UserAccount account) {
this.userId = id;
this.userAccount = account;
}
}
int capacity;
Map<String, Node> cache = new HashMap<>();
Node head;
Node end;
/**
* Capacity of Cache.
*/
private int capacity;
/**
* Cache {@link HashMap}.
*/
private Map<String, Node> cache = new HashMap<>();
/**
* Head.
*/
private Node head;
/**
* End.
*/
private Node end;
public LruCache(int capacity) {
this.capacity = capacity;
/**
* Constructor.
*
* @param cap Integer.
*/
public LruCache(final int cap) {
this.capacity = cap;
}
/**
* Get user account.
*
* @param userId String
* @return {@link UserAccount}
*/
public UserAccount get(String userId) {
public UserAccount get(final String userId) {
if (cache.containsKey(userId)) {
var node = cache.get(userId);
remove(node);
@ -75,8 +117,10 @@ public class LruCache {
/**
* Remove node from linked list.
*
* @param node {@link Node}
*/
public void remove(Node node) {
public void remove(final Node node) {
if (node.previous != null) {
node.previous.next = node.next;
} else {
@ -91,8 +135,10 @@ public class LruCache {
/**
* Move node to the front of the list.
*
* @param node {@link Node}
*/
public void setHead(Node node) {
public void setHead(final Node node) {
node.next = head;
node.previous = null;
if (head != null) {
@ -106,8 +152,11 @@ public class LruCache {
/**
* Set user account.
*
* @param userAccount {@link UserAccount}
* @param userId {@link String}
*/
public void set(String userId, UserAccount userAccount) {
public void set(final String userId, final UserAccount userAccount) {
if (cache.containsKey(userId)) {
var old = cache.get(userId);
old.userAccount = userAccount;
@ -127,25 +176,43 @@ public class LruCache {
}
}
public boolean contains(String userId) {
/**
* Check if Cache contains the userId.
*
* @param userId {@link String}
* @return boolean
*/
public boolean contains(final String userId) {
return cache.containsKey(userId);
}
/**
* Invalidate cache for user.
*
* @param userId {@link String}
*/
public void invalidate(String userId) {
public void invalidate(final String userId) {
var toBeRemoved = cache.remove(userId);
if (toBeRemoved != null) {
LOGGER.info("# {} has been updated! Removing older version from cache...", userId);
LOGGER.info("# {} has been updated! "
+ "Removing older version from cache...", userId);
remove(toBeRemoved);
}
}
/**
* Check if the cache is full.
* @return boolean
*/
public boolean isFull() {
return cache.size() >= capacity;
}
/**
* Get LRU data.
*
* @return {@link UserAccount}
*/
public UserAccount getLruData() {
return end.userAccount;
}
@ -161,6 +228,8 @@ public class LruCache {
/**
* Returns cache data in list form.
*
* @return {@link List}
*/
public List<UserAccount> getCacheDataInListForm() {
var listOfCacheData = new ArrayList<UserAccount>();
@ -174,10 +243,14 @@ public class LruCache {
/**
* Set cache capacity.
*
* @param newCapacity int
*/
public void setCapacity(int newCapacity) {
public void setCapacity(final int newCapacity) {
if (capacity > newCapacity) {
clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll
// Behavior can be modified to accommodate
// for decrease in cache size. For now, we'll
clear();
// just clear the cache.
} else {
this.capacity = newCapacity;

View File

@ -24,19 +24,28 @@
package com.iluwatar.caching;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Entity class (stored in cache and DB) used in the application.
*/
@Setter
@Getter
@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class UserAccount {
/**
* User Id.
*/
private String userId;
/**
* User Name.
*/
private String userName;
/**
* Additional Info.
*/
private String additionalInfo;
}

View File

@ -26,11 +26,27 @@ package com.iluwatar.caching.constants;
/**
* Constant class for defining constants.
*/
public class CachingConstants {
public final class CachingConstants {
/**
* User Account.
*/
public static final String USER_ACCOUNT = "user_accounts";
/**
* User ID.
*/
public static final String USER_ID = "userID";
/**
* User Name.
*/
public static final String USER_NAME = "userName";
/**
* Additional Info.
*/
public static final String ADD_INFO = "additionalInfo";
/**
* Constructor.
*/
private CachingConstants() {
}
}

View File

@ -0,0 +1,4 @@
/**
* Constants.
*/
package com.iluwatar.caching.constants;

View File

@ -0,0 +1,52 @@
package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
/**
* <p>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.</p>
*/
public interface DbManager {
/**
* Connect to DB.
*/
void connect();
/**
* Disconnect from DB.
*/
void disconnect();
/**
* Read from DB.
*
* @param userId {@link String}
* @return {@link UserAccount}
*/
UserAccount readFromDb(String userId);
/**
* Write to DB.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
UserAccount writeToDb(UserAccount userAccount);
/**
* Update record.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
UserAccount updateDb(UserAccount userAccount);
/**
* Update record or Insert if not exists.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
UserAccount upsertDb(UserAccount userAccount);
}

View File

@ -0,0 +1,25 @@
package com.iluwatar.caching.database;
/**
* Creates the database connection accroding the input parameter.
*/
public final class DbManagerFactory {
/**
* Private constructor.
*/
private DbManagerFactory() {
}
/**
* Init database.
*
* @param isMongo boolean
* @return {@link DbManager}
*/
public static DbManager initDb(final boolean isMongo) {
if (isMongo) {
return new MongoDb();
}
return new VirtualDb();
}
}

View File

@ -0,0 +1,128 @@
package com.iluwatar.caching.database;
import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
/**
* Implementation of DatabaseManager.
* implements base methods to work with MongoDb.
*/
@Slf4j
public class MongoDb implements DbManager {
private static final String DATABASE_NAME = "admin";
private static final String MONGO_USER = "root";
private static final String MONGO_PASSWORD = "rootpassword";
private MongoClient client;
private MongoDatabase db;
/**
* Connect to Db. Check th connection
*/
@Override
public void connect() {
MongoCredential mongoCredential = MongoCredential.createCredential(MONGO_USER,
DATABASE_NAME,
MONGO_PASSWORD.toCharArray());
MongoClientOptions options = MongoClientOptions.builder().build();
client = new MongoClient(new ServerAddress(), List.of(mongoCredential), options);
db = client.getDatabase(DATABASE_NAME);
}
@Override
public void disconnect() {
client.close();
}
/**
* Read data from DB.
*
* @param userId {@link String}
* @return {@link UserAccount}
*/
@Override
public UserAccount readFromDb(final String userId) {
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
.find(new Document(USER_ID, userId));
if (iterable.first() == null) {
return null;
}
Document doc = iterable.first();
if (doc != null) {
String userName = doc.getString(USER_NAME);
String appInfo = doc.getString(ADD_INFO);
return new UserAccount(userId, userName, appInfo);
} else {
return null;
}
}
/**
* Write data to DB.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount writeToDb(final UserAccount userAccount) {
db.getCollection(USER_ACCOUNT).insertOne(
new Document(USER_ID, userAccount.getUserId())
.append(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo())
);
return userAccount;
}
/**
* Update DB.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount updateDb(final UserAccount userAccount) {
Document id = new Document(USER_ID, userAccount.getUserId());
Document dataSet = new Document(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo());
db.getCollection(CachingConstants.USER_ACCOUNT)
.updateOne(id, new Document("$set", dataSet));
return userAccount;
}
/**
* Update data if exists.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount upsertDb(final UserAccount userAccount) {
String userId = userAccount.getUserId();
String userName = userAccount.getUserName();
String additionalInfo = userAccount.getAdditionalInfo();
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
new Document(USER_ID, userId),
new Document("$set",
new Document(USER_ID, userId)
.append(USER_NAME, userName)
.append(ADD_INFO, additionalInfo)
),
new UpdateOptions().upsert(true)
);
return userAccount;
}
}

View File

@ -0,0 +1,78 @@
package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
import java.util.HashMap;
import java.util.Map;
/**
* Implementation of DatabaseManager.
* implements base methods to work with hashMap as database.
*/
public class VirtualDb implements DbManager {
/**
* Virtual DataBase.
*/
private Map<String, UserAccount> db;
/**
* Creates new HashMap.
*/
@Override
public void connect() {
db = new HashMap<>();
}
@Override
public void disconnect() {
db = null;
}
/**
* Read from Db.
*
* @param userId {@link String}
* @return {@link UserAccount}
*/
@Override
public UserAccount readFromDb(final String userId) {
if (db.containsKey(userId)) {
return db.get(userId);
}
return null;
}
/**
* Write to DB.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount writeToDb(final UserAccount userAccount) {
db.put(userAccount.getUserId(), userAccount);
return userAccount;
}
/**
* Update reecord in DB.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount updateDb(final UserAccount userAccount) {
return writeToDb(userAccount);
}
/**
* Update.
*
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@Override
public UserAccount upsertDb(final UserAccount userAccount) {
return updateDb(userAccount);
}
}

View File

@ -0,0 +1,4 @@
/**
* Database classes.
*/
package com.iluwatar.caching.database;

View File

@ -0,0 +1,20 @@
/**
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.caching;

View File

@ -25,25 +25,21 @@ package com.iluwatar.caching;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that Caching example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* <p>
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -23,11 +23,11 @@
package com.iluwatar.caching;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Application test
*/
@ -43,32 +43,30 @@ class CachingTest {
// 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.initDb(false);
AppManager.initCacheCapacity(3);
app = new App();
app = new App(false);
}
@Test
void testReadAndWriteThroughStrategy() {
assertNotNull(app);
assertNotNull(app);
app.useReadAndWriteThroughStrategy();
}
@Test
void testReadThroughAndWriteAroundStrategy() {
assertNotNull(app);
assertNotNull(app);
app.useReadThroughAndWriteAroundStrategy();
}
@Test
void testReadThroughAndWriteBehindStrategy() {
assertNotNull(app);
assertNotNull(app);
app.useReadThroughAndWriteBehindStrategy();
}
@Test
void testCacheAsideStrategy() {
assertNotNull(app);
assertNotNull(app);
app.useCacheAsideStategy();
}
}

View File

@ -0,0 +1,84 @@
package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.internal.util.reflection.Whitebox;
import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class MongoDbTest {
private static final String ID = "123";
private static final String NAME = "Some user";
private static final String ADDITIONAL_INFO = "Some app Info";
@Mock
MongoDatabase db;
private MongoDb mongoDb = new MongoDb();
private UserAccount userAccount;
@BeforeEach
void init() {
db = mock(MongoDatabase.class);
Whitebox.setInternalState(mongoDb, "db", db);
userAccount = new UserAccount(ID, NAME, ADDITIONAL_INFO);
}
@Test
void connect() {
assertDoesNotThrow(() -> mongoDb.connect());
}
@Test
void readFromDb() {
Document document = new Document(USER_ID, ID)
.append(USER_NAME, NAME)
.append(ADD_INFO, ADDITIONAL_INFO);
MongoCollection<Document> mongoCollection = mock(MongoCollection.class);
when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
FindIterable<Document> findIterable = mock(FindIterable.class);
when(mongoCollection.find(any(Document.class))).thenReturn(findIterable);
when(findIterable.first()).thenReturn(document);
assertEquals(mongoDb.readFromDb(ID),userAccount);
}
@Test
void writeToDb() {
MongoCollection<Document> mongoCollection = mock(MongoCollection.class);
when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
doNothing().when(mongoCollection).insertOne(any(Document.class));
assertDoesNotThrow(()-> {mongoDb.writeToDb(userAccount);});
}
@Test
void updateDb() {
MongoCollection<Document> mongoCollection = mock(MongoCollection.class);
when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
assertDoesNotThrow(()-> {mongoDb.updateDb(userAccount);});
}
@Test
void upsertDb() {
MongoCollection<Document> mongoCollection = mock(MongoCollection.class);
when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
assertDoesNotThrow(()-> {mongoDb.upsertDb(userAccount);});
}
}

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Callback
folder: callback
permalink: /patterns/callback/
categories: Idiom
category: Idiom
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Chain of responsibility
folder: chain-of-responsibility
permalink: /patterns/chain-of-responsibility/
categories: Behavioral
category: Behavioral
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Circuit Breaker
folder: circuit-breaker
permalink: /patterns/circuit-breaker/
categories: Behavioral
category: Behavioral
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Static Content Hosting
folder: cloud-static-content-hosting
permalink: /patterns/cloud-static-content-hosting/
categories: Cloud
category: Cloud
language: en
tags:
- Cloud distributed

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Collection Pipeline
folder: collection-pipeline
permalink: /patterns/collection-pipeline/
categories: Functional
category: Functional
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Combinator
folder: combinator
permalink: /patterns/combinator/
categories: Idiom
category: Idiom
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
---
title: Command
folder: command
permalink: /patterns/command/
categories: Behavioral
category: Behavioral
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Commander
folder: commander
permalink: /patterns/commander/
categories: Concurrency
category: Concurrency
language: en
tags:
- Cloud distributed
@ -25,4 +22,4 @@ We need a mechanism in place which can handle these kinds of situations. We have
## Credits
* [https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/]
* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/)

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Composite Entity
folder: composite-entity
permalink: /patterns/composite-entity/
categories: Structural
category: Structural
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Composite
folder: composite
permalink: /patterns/composite/
categories: Structural
category: Structural
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Converter
folder: converter
permalink: /patterns/converter/
categories: Creational
category: Creational
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: CQRS
folder: cqrs
permalink: /patterns/cqrs/
categories: Architectural
category: Architectural
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Data Access Object
folder: dao
permalink: /patterns/dao/
categories: Architectural
category: Architectural
language: en
tags:
- Data access

View File

@ -1,10 +1,6 @@
---
layout: pattern
title: Data Bus
folder: data-bus
permalink: /patterns/data-bus/
categories: Architectural
category: Architectural
language: en
tags:
- Decoupling

View File

@ -46,7 +46,7 @@ class DataBusTest {
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);
}
@Test

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Data Locality
folder: data-locality
permalink: /patterns/data-locality/
categories: Behavioral
category: Behavioral
language: en
tags:
- Game programming

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Data Mapper
folder: data-mapper
permalink: /patterns/data-mapper/
categories: Architectural
category: Architectural
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Data Transfer Object
folder: data-transfer-object
permalink: /patterns/data-transfer-object/
categories: Architectural
category: Architectural
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Decorator
folder: decorator
permalink: /patterns/decorator/
categories: Structural
category: Structural
language: en
tags:
- Gang of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Delegation
folder: delegation
permalink: /patterns/delegation/
categories: Structural
category: Structural
language: en
tags:
- Decoupling

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Dependency Injection
folder: dependency-injection
permalink: /patterns/dependency-injection/
categories: Creational
category: Creational
language: en
tags:
- Decoupling
@ -103,4 +100,4 @@ Use the Dependency Injection pattern when:
* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd)
* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871)
* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1)
* [Google Guice Tutorial: Open source Java based dependency injection framework](https://www.amazon.com/gp/product/B083P7DZ8M/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B083P7DZ8M&linkId=04f0f902c877921e45215b624a124bfe)
* [Google Guice: Agile Lightweight Dependency Injection Framework](https://www.amazon.com/gp/product/1590599977/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1590599977&linkId=3b10c90b7ba480a1b7777ff38000f956)

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Dirty Flag
folder: dirty-flag
permalink: /patterns/dirty-flag/
categories: Behavioral
category: Behavioral
language: en
tags:
- Game programming

View File

@ -1,14 +1,15 @@
---
layout: pattern
title: Domain Model
folder: domain-model
permalink: /patterns/domain-model/
categories: Architectural
category: Architectural
language: en
tags:
- Domain
---
## Intent
Domain model pattern provides an object-oriented way of dealing with complicated logic. Instead of having one procedure that handles all business logic for a user action there are multiple objects and each of them handles a slice of domain logic that is relevant to it.
## Explanation
Real world example

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Double Buffer
folder: double-buffer
permalink: /patterns/double-buffer/
categories: Behavioral
category: Behavioral
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Double Checked Locking
folder: double-checked-locking
permalink: /patterns/double-checked-locking/
categories: Idiom
category: Idiom
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Double Dispatch
folder: double-dispatch
permalink: /patterns/double-dispatch/
categories: Idiom
category: Idiom
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: EIP Aggregator
folder: eip-aggregator
permalink: /patterns/eip-aggregator/
categories: Integration
category: Integration
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: EIP Message Channel
folder: eip-message-channel
permalink: /patterns/eip-message-channel/
categories: Integration
category: Integration
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: EIP Publish Subscribe
folder: eip-publish-subscribe
permalink: /patterns/eip-publish-subscribe/
categories: Integration
category: Integration
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: EIP Splitter
folder: eip-splitter
permalink: /patterns/eip-splitter/
categories: Integration
category: Integration
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: EIP Wire Tap
folder: eip-wire-tap
permalink: /patterns/eip-wire-tap/
categories: Integration
category: Integration
language: en
tags:
- Enterprise Integration Pattern

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Event Aggregator
folder: event-aggregator
permalink: /patterns/event-aggregator/
categories: Structural
category: Structural
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Event-based Asynchronous
folder: event-asynchronous
permalink: /patterns/event-asynchronous/
categories: Concurrency
category: Concurrency
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Event Driven Architecture
folder: event-driven-architecture
permalink: /patterns/event-driven-architecture/
categories: Architectural
category: Architectural
language: en
tags:
- Reactive
@ -24,7 +21,6 @@ Use an Event-driven architecture when
## Real world examples
* SendGrid, an email API, sends events whenever an email is processed, delivered, opened etc... (https://sendgrid.com/docs/API_Reference/Webhooks/event.html)
* Chargify, a billing API, exposes payment activity through various events (https://docs.chargify.com/api-events)
* Amazon's AWS Lambda, lets you execute code in response to events such as changes to Amazon S3 buckets, updates to an Amazon DynamoDB table, or custom events generated by your applications or devices. (https://aws.amazon.com/lambda)
* MySQL runs triggers based on events such as inserts and update events happening on database tables.
@ -32,6 +28,6 @@ Use an Event-driven architecture when
## Credits
* [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture)
* [Fundamental Components of an Event-Driven Architecture](http://giocc.com/fundamental-components-of-an-event-driven-architecture.html)
* [What is an Event-Driven Architecture](https://aws.amazon.com/event-driven-architecture/)
* [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications)
* [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture)

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Event Queue
folder: event-queue
permalink: /patterns/event-queue/
categories: Concurrency
category: Concurrency
language: en
tags:
- Game programming

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Event Sourcing
folder: event-sourcing
permalink: /patterns/event-sourcing/
categories: Architectural
category: Architectural
language: en
tags:
- Performance

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Execute Around
folder: execute-around
permalink: /patterns/execute-around/
categories: Idiom
category: Idiom
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Extension objects
folder: extension-objects
permalink: /patterns/extension-objects/
categories: Behavioral
category: Behavioral
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Facade
folder: facade
permalink: /patterns/facade/
categories: Structural
category: Structural
language: en
tags:
- Gang Of Four

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Factory Kit
folder: factory-kit
permalink: /patterns/factory-kit/
categories: Creational
category: Creational
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Factory Method
folder: factory-method
permalink: /patterns/factory-method/
categories: Creational
category: Creational
language: en
tags:
- Extensibility

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Factory
folder: factory
permalink: /patterns/factory/
categories: Creational
category: Creational
language: en
tags:
- Gang of Four

117
fanout-fanin/README.md Normal file
View File

@ -0,0 +1,117 @@
---
title: Fan-Out/Fan-In
category: Integration
language: en
tags:
- Microservices
---
## Intent
The pattern is used when a source system needs to run one or more long-running processes that will fetch some data.
The source will not block itself waiting for the reply. <br> The pattern will run the same function in multiple
services or machines to fetch the data. This is equivalent to invoking the function multiple times on different chunks of data.
## Explanation
The FanOut/FanIn service will take in a list of requests and a consumer. Each request might complete at a different time.
FanOut/FanIn service will accept the input params and returns the initial system an ID to acknowledge that the pattern
service has received the requests. Now the caller will not wait or expect the result in the same connection.
Meanwhile, the pattern service will invoke the requests that have come. The requests might complete at different time.
These requests will be processed in different instances of the same function in different machines or services. As the
requests get completed, a callback service everytime is called that transforms the result into a common single object format
that gets pushed to a consumer. The caller will be at the other end of the consumer receiving the result.
**Programmatic Example**
The implementation provided has a list of numbers and end goal is to square the numbers and add them to a single result.
`FanOutFanIn` class receives the list of numbers in the form of list of `SquareNumberRequest` and a `Consumer` instance
that collects the results as the requests get over. `SquareNumberRequest` will square the number with a random delay
to give the impression of a long-running process that can complete at any time. `Consumer` instance will add the results from
different `SquareNumberRequest` that will come random time instances.
Let's look at `FanOutFanIn` class that fans out the requests in async processes.
```java
public class FanOutFanIn {
public static Long fanOutFanIn(
final List<SquareNumberRequest> requests, final Consumer consumer) {
ExecutorService service = Executors.newFixedThreadPool(requests.size());
// fanning out
List<CompletableFuture<Void>> futures =
requests.stream()
.map(
request ->
CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
return consumer.getSumOfSquaredNumbers().get();
}
}
```
`Consumer` is used a callback class that will be called when a request is completed. This will aggregate
the result from all requests.
```java
public class Consumer {
private final AtomicLong sumOfSquaredNumbers;
Consumer(Long init) {
sumOfSquaredNumbers = new AtomicLong(init);
}
public Long add(final Long num) {
return sumOfSquaredNumbers.addAndGet(num);
}
}
```
Request is represented as a `SquareNumberRequest` that squares the number with random delay and calls the
`Consumer` once it is squared.
```java
public class SquareNumberRequest {
private final Long number;
public void delayedSquaring(final Consumer consumer) {
var minTimeOut = 5000L;
SecureRandom secureRandom = new SecureRandom();
var randomTimeOut = secureRandom.nextInt(2000);
try {
// this will make the thread sleep from 5-7s.
Thread.sleep(minTimeOut + randomTimeOut);
} catch (InterruptedException e) {
LOGGER.error("Exception while sleep ", e);
Thread.currentThread().interrupt();
} finally {
consumer.add(number * number);
}
}
}
```
## Class diagram
![alt-text](./etc/fanout-fanin.png)
## Applicability
Use this pattern when you can divide the workload into multiple chunks that can be dealt with separately.
## Related patterns
* [Aggregator Microservices](https://java-design-patterns.com/patterns/aggregator-microservices/)
* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/)
## Credits
* [Understanding Azure Durable Functions - Part 8: The Fan Out/Fan In Pattern](http://dontcodetired.com/blog/post/Understanding-Azure-Durable-Functions-Part-8-The-Fan-OutFan-In-Pattern)
* [Fan-out/fan-in scenario in Durable Functions - Cloud backup example](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup)
* [Understanding the Fan-Out/Fan-In API Integration Pattern](https://dzone.com/articles/understanding-the-fan-out-fan-in-api-integration-p)

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,39 @@
@startuml
package com.iluwatar.fanout.fanin {
class App {
- LOGGER : Logger {static}
+ App()
+ main(args : String[]) {static}
}
class Consumer {
- sumOfSquaredNumbers : AtomicLong
~ Consumer(init : Long)
+ add(num : Long) : Long
+ getSumOfSquaredNumbers() : AtomicLong
}
class FanOutFanIn {
+ FanOutFanIn()
+ fanOutFanIn(requests : List<SquareNumberRequest>, consumer : Consumer) : Long {static}
}
class SquareNumberRequest {
- LOGGER : Logger {static}
- number : Long
+ SquareNumberRequest(number : Long)
+ delayedSquaring(consumer : Consumer)
}
object SquareNumberRequest1
object SquareNumberRequest2
object SquareNumberRequest3
diamond dia
}
App --> FanOutFanIn
FanOutFanIn --> "fan out - running in parallel" SquareNumberRequest1
FanOutFanIn --> "fan out" SquareNumberRequest2
FanOutFanIn --> "fan out" SquareNumberRequest3
SquareNumberRequest1 --> "fan in - aggregate using callback" dia
SquareNumberRequest2 --> "fan in" dia
SquareNumberRequest3 --> "fan in" dia
dia --> Consumer
@enduml

69
fanout-fanin/pom.xml Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License (MIT)
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Module Model-view-viewmodel is using ZK framework
ZK framework is licensed under LGPL and the license can be found at lgpl-3.0.txt
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fanout-fanin</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.fanout.fanin.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,74 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* FanOut/FanIn pattern is a concurrency pattern that refers to executing multiple instances of the
* activity function concurrently. The "fan out" part is essentially splitting the data into
* multiple chunks and then calling the activity function multiple times, passing the chunks.
*
* <p>When each chunk has been processed, the "fan in" takes place that aggregates results from each
* instance of function and forms a single final result.
*
* <p>This pattern is only really useful if you can “chunk” the workload in a meaningful way for
* splitting up to be processed in parallel.
*/
@Slf4j
public class App {
/**
* Entry point.
*
* <p>Implementation provided has a list of numbers that has to be squared and added. The list can
* be chunked in any way and the "activity function" {@link
* SquareNumberRequest#delayedSquaring(Consumer)} i.e. squaring the number ca be done
* concurrently. The "fan in" part is handled by the {@link Consumer} that takes in the result
* from each instance of activity and aggregates it whenever that particular activity function
* gets over.
*/
public static void main(String[] args) {
final List<Long> numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L);
LOGGER.info("Numbers to be squared and get sum --> {}", numbers);
final List<SquareNumberRequest> requests =
numbers.stream().map(SquareNumberRequest::new).collect(Collectors.toList());
var consumer = new Consumer(0L);
// Pass the request and the consumer to fanOutFanIn or sometimes referred as Orchestrator
// function
final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer);
LOGGER.info("Sum of all squared numbers --> {}", sumOfSquaredNumbers);
}
}

View File

@ -0,0 +1,48 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import java.util.concurrent.atomic.AtomicLong;
import lombok.Getter;
/**
* Consumer or callback class that will be called everytime a request is complete This will
* aggregate individual result to form a final result.
*/
@Getter
public class Consumer {
private final AtomicLong sumOfSquaredNumbers;
Consumer(Long init) {
sumOfSquaredNumbers = new AtomicLong(init);
}
public Long add(final Long num) {
return sumOfSquaredNumbers.addAndGet(num);
}
}

View File

@ -0,0 +1,62 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* FanOutFanIn class processes long running requests, when any of the processes gets over, result is
* passed over to the consumer or the callback function. Consumer will aggregate the results as they
* keep on completing.
*/
public class FanOutFanIn {
/**
* the main fanOutFanIn function or orchestrator function.
* @param requests List of numbers that need to be squared and summed up
* @param consumer Takes in the squared number from {@link SquareNumberRequest} and sums it up
* @return Aggregated sum of all squared numbers.
*/
public static Long fanOutFanIn(
final List<SquareNumberRequest> requests, final Consumer consumer) {
ExecutorService service = Executors.newFixedThreadPool(requests.size());
// fanning out
List<CompletableFuture<Void>> futures =
requests.stream()
.map(
request ->
CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
return consumer.getSumOfSquaredNumbers().get();
}
}

View File

@ -0,0 +1,63 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import java.security.SecureRandom;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* Squares the number with a little timeout to give impression of long running process that return
* at different times.
*/
@Slf4j
@AllArgsConstructor
public class SquareNumberRequest {
private final Long number;
/**
* Squares the number with a little timeout to give impression of long running process that return
* at different times.
* @param consumer callback class that takes the result after the delay.
* */
public void delayedSquaring(final Consumer consumer) {
var minTimeOut = 5000L;
SecureRandom secureRandom = new SecureRandom();
var randomTimeOut = secureRandom.nextInt(2000);
try {
// this will make the thread sleep from 5-7s.
Thread.sleep(minTimeOut + randomTimeOut);
} catch (InterruptedException e) {
LOGGER.error("Exception while sleep ", e);
Thread.currentThread().interrupt();
} finally {
consumer.add(number * number);
}
}
}

View File

@ -0,0 +1,36 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class AppTest {
@Test
void shouldLaunchApp() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -0,0 +1,47 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class FanOutFanInTest {
@Test
void fanOutFanInTest() {
final List<Long> numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L);
final List<SquareNumberRequest> requests =
numbers.stream().map(SquareNumberRequest::new).collect(Collectors.toList());
final Consumer consumer = new Consumer(0L);
final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer);
Assertions.assertEquals(139, sumOfSquaredNumbers);
}
}

View File

@ -0,0 +1,41 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.fanout.fanin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class SquareNumberRequestTest {
@Test
void delayedSquaringTest() {
Consumer consumer = new Consumer(10L);
SquareNumberRequest squareNumberRequest = new SquareNumberRequest(5L);
squareNumberRequest.delayedSquaring(consumer);
Assertions.assertEquals(35, consumer.getSumOfSquaredNumbers().get());
}
}

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Feature Toggle
folder: feature-toggle
permalink: /patterns/feature-toggle/
categories: Behavioral
category: Behavioral
language: en
tags:
- Extensibility

View File

@ -1,12 +1,8 @@
---
layout: pattern
title: Filterer
folder: filterer
permalink: /patterns/filterer/
description: Design pattern that helps container-like objects to return filtered version of themselves.# short meta description that shows in Google search results
language: en
categories:
- Functional
category: Functional
tags:
- Extensibility
---

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Fluent Interface
folder: fluentinterface
permalink: /patterns/fluentinterface/
categories: Functional
category: Functional
language: en
tags:
- Reactive

View File

@ -1,9 +1,6 @@
---
layout: pattern
title: Flux
folder: flux
permalink: /patterns/flux/
categories: Structural
category: Structural
language: en
tags:
- Decoupling

Some files were not shown because too many files have changed in this diff Show More