Compare commits

..

2 Commits

212 changed files with 1179 additions and 6922 deletions

View File

@ -1550,87 +1550,6 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "marlo2222",
"name": "Marlo Henrique",
"avatar_url": "https://avatars.githubusercontent.com/u/40809563?v=4",
"profile": "https://github.com/marlo2222",
"contributions": [
"translation"
]
},
{
"login": "AndriyPyzh",
"name": "AndriyPyzh",
"avatar_url": "https://avatars.githubusercontent.com/u/57706635?v=4",
"profile": "https://github.com/AndriyPyzh",
"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, "contributorsPerLine": 4,

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright <EFBFBD> 2014-2021 Ilkka Sepp<EFBFBD>l<EFBFBD> Copyright (c) 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -19,6 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. 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

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) [![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) [![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-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-179-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-170-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/> <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), [**id**](localization/id/README.md), [**ru**](localization/ru/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)
<br/> <br/>
@ -330,19 +330,6 @@ This project is licensed under the terms of the MIT license.
<tr> <tr>
<td align="center"><a href="https://github.com/Xenilo137"><img src="https://avatars.githubusercontent.com/u/24865069?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xenilo137</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Xenilo137" title="Code">💻</a></td> <td align="center"><a href="https://github.com/Xenilo137"><img src="https://avatars.githubusercontent.com/u/24865069?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xenilo137</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Xenilo137" title="Code">💻</a></td>
<td align="center"><a href="https://www.linkedin.com/in/souzasamuel/"><img src="https://avatars.githubusercontent.com/u/17254162?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Samuel Souza</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=samuelpsouza" title="Code">💻</a></td> <td align="center"><a href="https://www.linkedin.com/in/souzasamuel/"><img src="https://avatars.githubusercontent.com/u/17254162?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Samuel Souza</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=samuelpsouza" title="Code">💻</a></td>
<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> </tr>
</table> </table>

View File

@ -196,13 +196,13 @@ Example use cases
* Unit test case writing becomes much easier * Unit test case writing becomes much easier
* UI tools for different OS * UI tools for different OS
## Consequences ## Consequences:
* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time. * Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.
* While the pattern is great when creating predefined objects, adding the new ones might be challenging. * While the pattern is great when creating predefined objects, adding the new ones might be challenging.
* The code becomes more complicated than it should be since a lot of new interfaces and classes are introduced along with the pattern. * The code becomes more complicated than it should be since a lot of new interfaces and classes are introduced along with the pattern.
## Tutorials ## Tutorial
* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) * [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java)

View File

@ -11,7 +11,7 @@ tags:
## Intent ## 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 ## Explanation
@ -70,7 +70,7 @@ public abstract class ActiveCreature{
requests.put(new Runnable() { requests.put(new Runnable() {
@Override @Override
public void run() { public void run() {
logger.info("{} has started to roam the wastelands.",name()); logger.info("{} has started to roam and the wastelands.",name());
} }
} }
); );
@ -82,7 +82,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 invoke and execute methods. We can see that any class that will extend the ActiveCreature class will have its own thread of control to execute and invocate methods.
For example, the Orc class: For example, the Orc class:
@ -96,7 +96,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 ```java
public static void main(String[] args) { public static void main(String[] args) {
@ -123,4 +123,4 @@ Now, we can create multiple creatures such as Orcs, tell them to eat and roam, a
## Class diagram ## 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 the wastelands. * Roam in the wastelands.
* @throws InterruptedException due to firing a new Runnable. * @throws InterruptedException due to firing a new Runnable.
*/ */
public void roam() throws InterruptedException { public void roam() throws InterruptedException {

View File

@ -105,7 +105,7 @@ Use the Adapter pattern when
* you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class. * you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.
* most of the applications using third party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code. * most of the applications using third party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code.
## Consequences ## Consequences:
Class and object adapters have different trade-offs. A class adapter Class and object adapters have different trade-offs. A class adapter
* adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter wont work when we want to adapt a class and all its subclasses. * adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter wont work when we want to adapt a class and all its subclasses.
@ -118,7 +118,7 @@ An object adapter
* makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making Adapter refer to the subclass rather than the Adaptee itself. * makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making Adapter refer to the subclass rather than the Adaptee itself.
## Known uses ## Real world examples
* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) * [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) * [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-)

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ Decouple an abstraction from its implementation so that the two can vary indepen
## Explanation ## Explanation
Real-world example Real world example
> Consider you have a weapon with different enchantments, and you are supposed to allow mixing > Consider you have a weapon with different enchantments, and you are supposed to allow mixing
> different weapons with different enchantments. What would you do? Create multiple copies of each > different weapons with different enchantments. What would you do? Create multiple copies of each
@ -161,36 +161,27 @@ public class SoulEatingEnchantment implements Enchantment {
Here are both hierarchies in action: Here are both hierarchies in action:
```java ```java
LOGGER.info("The knight receives an enchanted sword.");
var enchantedSword = new Sword(new SoulEatingEnchantment()); var enchantedSword = new Sword(new SoulEatingEnchantment());
enchantedSword.wield(); enchantedSword.wield();
enchantedSword.swing(); enchantedSword.swing();
enchantedSword.unwield(); enchantedSword.unwield();
// The sword is wielded.
// The item spreads bloodlust.
// The sword is swinged.
// The item eats the soul of enemies.
// The sword is unwielded.
// Bloodlust slowly disappears.
LOGGER.info("The valkyrie receives an enchanted hammer.");
var hammer = new Hammer(new FlyingEnchantment()); var hammer = new Hammer(new FlyingEnchantment());
hammer.wield(); hammer.wield();
hammer.swing(); hammer.swing();
hammer.unwield(); hammer.unwield();
``` // The hammer is wielded.
// The item begins to glow faintly.
Here's the console output. // The hammer is swinged.
// The item flies and strikes the enemies finally returning to owner's hand.
``` // The hammer is unwielded.
The knight receives an enchanted sword. // The item's glow fades.
The sword is wielded.
The item spreads bloodlust.
The sword is swung.
The item eats the soul of enemies.
The sword is unwielded.
Bloodlust slowly disappears.
The valkyrie receives an enchanted hammer.
The hammer is wielded.
The item begins to glow faintly.
The hammer is swung.
The item flies and strikes the enemies finally returning to owner's hand.
The hammer is unwielded.
The item's glow fades.
``` ```
## Class diagram ## Class diagram

View File

@ -16,11 +16,11 @@ process can create different representations.
## Explanation ## Explanation
Real-world example Real world example
> Imagine a character generator for a role-playing game. The easiest option is to let the computer > Imagine a character generator for a role-playing game. The easiest option is to let the computer
> create the character for you. If you want to manually select the character details like > create the character for you. If you want to manually select the character details like
> profession, gender, hair color, etc. the character generation becomes a step-by-step process that > profession, gender, hair color etc. the character generation becomes a step-by-step process that
> completes when all the selections are ready. > completes when all the selections are ready.
In plain words In plain words
@ -49,7 +49,7 @@ anti-pattern.
**Programmatic Example** **Programmatic Example**
The sane alternative is to use the Builder pattern. First of all, we have our hero that we want to The sane alternative is to use the Builder pattern. First of all we have our hero that we want to
create: create:
```java ```java
@ -134,7 +134,7 @@ Use the Builder pattern when
* The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled * The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled
* The construction process must allow different representations for the object that's constructed * The construction process must allow different representations for the object that's constructed
## Known uses ## Real world examples
* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) * [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)
* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on. * [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on.

View File

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

View File

@ -1,11 +0,0 @@
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

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

View File

@ -1,62 +1,58 @@
/*
* 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; package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
import com.iluwatar.caching.database.DbManagerFactory;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* The Caching pattern describes how to avoid expensive re-acquisition of * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
* resources by not releasing the resources immediately after their use. * the resources immediately after their use. The resources retain their identity, are kept in some
* The resources retain their identity, are kept in some fast-access storage, * fast-access storage, and are re-used to avoid having to acquire them again. There are four main
* 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;
* caching strategies/techniques in this pattern; each with their own pros and * <code>write-through</code> which writes data to the cache and DB in a single transaction,
* cons. They are <code>write-through</code> which writes data to the cache and * <code>write-around</code> which writes data immediately into the DB instead of the cache,
* DB in a single transaction, <code>write-around</code> which writes data * <code>write-behind</code> which writes data into the cache initially whilst the data is only
* immediately into the DB instead of the cache, <code>write-behind</code> * written into the DB when the cache is full, and <code>cache-aside</code> which pushes the
* which writes data into the cache initially whilst the data is only * responsibility of keeping the data synchronized in both data sources to the application itself.
* written into the DB when the cache is full, and <code>cache-aside</code> * The <code>read-through</code> strategy is also included in the mentioned four strategies --
* which pushes the responsibility of keeping the data synchronized in both * returns data from the cache to the caller <b>if</b> it exists <b>else</b> queries from DB and
* data sources to the application itself. The <code>read-through</code> * stores it into the cache for future use. These strategies determine when the data in the cache
* strategy is also included in the mentioned four strategies -- * should be written back to the backing store (i.e. Database) and help keep both data sources
* returns data from the cache to the caller <b>if</b> it exists <b>else</b> * synchronized/up-to-date. This pattern can improve performance and also helps to maintain
* queries from DB and stores it into the cache for future use. These strategies * consistency between data held in the cache and the data in the underlying data store.
* 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 * <p>In this example, the user account ({@link UserAccount}) entity is used as the underlying
* as the underlying application data. The cache itself is implemented as an * application data. The cache itself is implemented as an internal (Java) data structure. It adopts
* internal (Java) data structure. It adopts a Least-Recently-Used (LRU) * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four
* 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
* strategies are individually tested. The testing of the cache is restricted * querying of user accounts from the underlying data store ( {@link DbManager}). The main class (
* towards saving and querying of user accounts from the * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and
* underlying data store( {@link DbManager}). The main class ( {@link App} * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager
* is not aware of the underlying mechanics of the application * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store
* (i.e. save and query) and whether the data is coming from the cache or the * (depending on the preferred caching policy/strategy).
* 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> * <p>
* <i>{@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> * <i>{@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager} </i>
* 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> * </p>
* *
* @see CacheStore * @see CacheStore
@ -65,67 +61,23 @@ import lombok.extern.slf4j.Slf4j;
*/ */
@Slf4j @Slf4j
public class App { 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. * Program entry point.
* *
* @param args command line args * @param args command line args
*/ */
public static void main(final String[] args) { public static void main(String[] args) {
// VirtualDB (instead of MongoDB) was used in running the JUnit tests AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
// and the App class to avoid Maven compilation errors. Set flag to // and the App class to avoid Maven compilation errors. Set flag to
// true to run the tests with MongoDB (provided that MongoDB is // true to run the tests with MongoDB (provided that MongoDB is
// installed and socket connection is open). // installed and socket connection is open).
boolean isDbMongo = isDbMongo(args); AppManager.initCacheCapacity(3);
if (isDbMongo) { var app = new App();
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(); app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy(); app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy(); app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy(); 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;
} }
/** /**
@ -133,14 +85,14 @@ public class App {
*/ */
public void useReadAndWriteThroughStrategy() { public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH"); LOGGER.info("# CachingPolicy.THROUGH");
appManager.initCachingPolicy(CachingPolicy.THROUGH); AppManager.initCachingPolicy(CachingPolicy.THROUGH);
var userAccount1 = new UserAccount("001", "John", "He is a boy."); var userAccount1 = new UserAccount("001", "John", "He is a boy.");
appManager.save(userAccount1); AppManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("001"); AppManager.find("001");
appManager.find("001"); AppManager.find("001");
} }
/** /**
@ -148,21 +100,21 @@ public class App {
*/ */
public void useReadThroughAndWriteAroundStrategy() { public void useReadThroughAndWriteAroundStrategy() {
LOGGER.info("# CachingPolicy.AROUND"); LOGGER.info("# CachingPolicy.AROUND");
appManager.initCachingPolicy(CachingPolicy.AROUND); AppManager.initCachingPolicy(CachingPolicy.AROUND);
var userAccount2 = new UserAccount("002", "Jane", "She is a girl."); var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
appManager.save(userAccount2); AppManager.save(userAccount2);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("002"); AppManager.find("002");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
userAccount2 = appManager.find("002"); userAccount2 = AppManager.find("002");
userAccount2.setUserName("Jane G."); userAccount2.setUserName("Jane G.");
appManager.save(userAccount2); AppManager.save(userAccount2);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("002"); AppManager.find("002");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("002"); AppManager.find("002");
} }
/** /**
@ -170,31 +122,23 @@ public class App {
*/ */
public void useReadThroughAndWriteBehindStrategy() { public void useReadThroughAndWriteBehindStrategy() {
LOGGER.info("# CachingPolicy.BEHIND"); LOGGER.info("# CachingPolicy.BEHIND");
appManager.initCachingPolicy(CachingPolicy.BEHIND); AppManager.initCachingPolicy(CachingPolicy.BEHIND);
var userAccount3 = new UserAccount("003", var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
"Adam", var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
"He likes food."); var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
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(userAccount3);
appManager.save(userAccount4); AppManager.save(userAccount4);
appManager.save(userAccount5); AppManager.save(userAccount5);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("003"); AppManager.find("003");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
UserAccount userAccount6 = new UserAccount("006", UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
"Yasha", AppManager.save(userAccount6);
"She is an only child."); LOGGER.info(AppManager.printCacheContent());
appManager.save(userAccount6); AppManager.find("004");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("004");
LOGGER.info(appManager.printCacheContent());
} }
/** /**
@ -202,26 +146,20 @@ public class App {
*/ */
public void useCacheAsideStategy() { public void useCacheAsideStategy() {
LOGGER.info("# CachingPolicy.ASIDE"); LOGGER.info("# CachingPolicy.ASIDE");
appManager.initCachingPolicy(CachingPolicy.ASIDE); AppManager.initCachingPolicy(CachingPolicy.ASIDE);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
var userAccount3 = new UserAccount("003", var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
"Adam", var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
"He likes food."); var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
var userAccount4 = new UserAccount("004", AppManager.save(userAccount3);
"Rita", AppManager.save(userAccount4);
"She hates cats."); AppManager.save(userAccount5);
var userAccount5 = new UserAccount("005",
"Isaac",
"He is allergic to mustard.");
appManager.save(userAccount3);
appManager.save(userAccount4);
appManager.save(userAccount5);
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("003"); AppManager.find("003");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
appManager.find("004"); AppManager.find("004");
LOGGER.info(appManager.printCacheContent()); LOGGER.info(AppManager.printCacheContent());
} }
} }

View File

@ -23,80 +23,65 @@
package com.iluwatar.caching; package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager; import java.text.ParseException;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* AppManager helps to bridge the gap in communication between the main class * AppManager helps to bridge the gap in communication between the main class and the application's
* and the application's back-end. DB connection is initialized through this * back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
* class. The chosen caching strategy/policy is also initialized here. * also initialized here. Before the cache can be used, the size of the cache has to be set.
* 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
* Depending on the chosen caching policy, AppManager will call the * CacheStore class.
* appropriate function in the CacheStore class.
*/ */
@Slf4j @Slf4j
public class AppManager { public final class AppManager {
/**
* Caching Policy.
*/
private CachingPolicy cachingPolicy;
/**
* Database Manager.
*/
private final DbManager dbManager;
/**
* Cache Store.
*/
private final CacheStore cacheStore;
/** private static CachingPolicy cachingPolicy;
* Constructor.
* private AppManager() {
* @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 * Developer/Tester is able to choose whether the application should use MongoDB as its underlying
* MongoDB as its underlying data storage or a simple Java data structure * data storage or a simple Java data structure to (temporarily) store the data/objects during
* to (temporarily) store the data/objects during runtime. * runtime.
*/ */
public void initDb() { public static void initDb(boolean useMongoDb) {
dbManager.connect(); if (useMongoDb) {
try {
DbManager.connect();
} catch (ParseException e) {
LOGGER.error("Error connecting to MongoDB", e);
}
} else {
DbManager.createVirtualDb();
}
} }
/** /**
* Initialize caching policy. * Initialize caching policy.
*
* @param policy is a {@link CachingPolicy}
*/ */
public void initCachingPolicy(final CachingPolicy policy) { public static void initCachingPolicy(CachingPolicy policy) {
cachingPolicy = policy; cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) { if (cachingPolicy == CachingPolicy.BEHIND) {
Runtime.getRuntime().addShutdownHook(new Thread(cacheStore::flushCache)); Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache));
} }
cacheStore.clearCache(); CacheStore.clearCache();
}
public static void initCacheCapacity(int capacity) {
CacheStore.initCapacity(capacity);
} }
/** /**
* Find user account. * Find user account.
*
* @param userId String
* @return {@link UserAccount}
*/ */
public UserAccount find(final String userId) { public static UserAccount find(String userId) {
LOGGER.info("Trying to find {} in cache", userId); if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
if (cachingPolicy == CachingPolicy.THROUGH return CacheStore.readThrough(userId);
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) { } else if (cachingPolicy == CachingPolicy.BEHIND) {
return cacheStore.readThroughWithWriteBackPolicy(userId); return CacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) { } else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId); return findAside(userId);
} }
@ -105,55 +90,41 @@ public class AppManager {
/** /**
* Save user account. * Save user account.
*
* @param userAccount {@link UserAccount}
*/ */
public void save(final UserAccount userAccount) { public static void save(UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) { if (cachingPolicy == CachingPolicy.THROUGH) {
cacheStore.writeThrough(userAccount); CacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) { } else if (cachingPolicy == CachingPolicy.AROUND) {
cacheStore.writeAround(userAccount); CacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) { } else if (cachingPolicy == CachingPolicy.BEHIND) {
cacheStore.writeBehind(userAccount); CacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) { } else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount); saveAside(userAccount);
} }
} }
/** public static String printCacheContent() {
* Returns String. return CacheStore.print();
*
* @return String
*/
public String printCacheContent() {
return cacheStore.print();
} }
/** /**
* Cache-Aside save user account helper. * Cache-Aside save user account helper.
*
* @param userAccount {@link UserAccount}
*/ */
private void saveAside(final UserAccount userAccount) { private static void saveAside(UserAccount userAccount) {
dbManager.updateDb(userAccount); DbManager.updateDb(userAccount);
cacheStore.invalidate(userAccount.getUserId()); CacheStore.invalidate(userAccount.getUserId());
} }
/** /**
* Cache-Aside find user account helper. * Cache-Aside find user account helper.
*
* @param userId String
* @return {@link UserAccount}
*/ */
private UserAccount findAside(final String userId) { private static UserAccount findAside(String userId) {
return Optional.ofNullable(cacheStore.get(userId)) return Optional.ofNullable(CacheStore.get(userId))
.or(() -> { .or(() -> {
Optional<UserAccount> userAccount = Optional<UserAccount> userAccount = Optional.ofNullable(DbManager.readFromDb(userId));
Optional.ofNullable(dbManager.readFromDb(userId)); userAccount.ifPresent(account -> CacheStore.set(userId, account));
userAccount.ifPresent(account -> cacheStore.set(userId, account)); return userAccount;
return userAccount; })
}) .orElse(null);
.orElse(null);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,52 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,128 +0,0 @@
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

@ -1,78 +0,0 @@
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

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

View File

@ -1,20 +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;

View File

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

View File

@ -23,11 +23,11 @@
package com.iluwatar.caching; package com.iluwatar.caching;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/** /**
* Application test * Application test
*/ */
@ -43,30 +43,32 @@ class CachingTest {
// to avoid Maven compilation errors. Set flag to true to run the // to avoid Maven compilation errors. Set flag to true to run the
// tests with MongoDB (provided that MongoDB is installed and socket // tests with MongoDB (provided that MongoDB is installed and socket
// connection is open). // connection is open).
app = new App(false); AppManager.initDb(false);
AppManager.initCacheCapacity(3);
app = new App();
} }
@Test @Test
void testReadAndWriteThroughStrategy() { void testReadAndWriteThroughStrategy() {
assertNotNull(app); assertNotNull(app);
app.useReadAndWriteThroughStrategy(); app.useReadAndWriteThroughStrategy();
} }
@Test @Test
void testReadThroughAndWriteAroundStrategy() { void testReadThroughAndWriteAroundStrategy() {
assertNotNull(app); assertNotNull(app);
app.useReadThroughAndWriteAroundStrategy(); app.useReadThroughAndWriteAroundStrategy();
} }
@Test @Test
void testReadThroughAndWriteBehindStrategy() { void testReadThroughAndWriteBehindStrategy() {
assertNotNull(app); assertNotNull(app);
app.useReadThroughAndWriteBehindStrategy(); app.useReadThroughAndWriteBehindStrategy();
} }
@Test @Test
void testCacheAsideStrategy() { void testCacheAsideStrategy() {
assertNotNull(app); assertNotNull(app);
app.useCacheAsideStategy(); app.useCacheAsideStategy();
} }
} }

View File

@ -1,84 +0,0 @@
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,8 +1,8 @@
--- ---
layout: pattern layout: pattern
title: Chain of responsibility title: Chain of responsibility
folder: chain-of-responsibility folder: chain
permalink: /patterns/chain-of-responsibility/ permalink: /patterns/chain/
categories: Behavioral categories: Behavioral
language: en language: en
tags: tags:
@ -10,17 +10,16 @@ tags:
--- ---
## Intent ## Intent
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to Avoid coupling the sender of a request to its receiver by giving more than one object a chance to
handle the request. Chain the receiving objects and pass the request along the chain until an object handle the request. Chain the receiving objects and pass the request along the chain until an object
handles it. handles it.
## Explanation ## Explanation
Real-world example Real world example
> The Orc King gives loud orders to his army. The closest one to react is the commander, then > The Orc King gives loud orders to his army. The closest one to react is the commander, then
> an officer, and then a soldier. The commander, officer, and soldier form a chain of responsibility. > officer and then soldier. The commander, officer and soldier here form a chain of responsibility.
In plain words In plain words
@ -36,7 +35,7 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
Translating our example with the orcs from above. First, we have the `Request` class: Translating our example with the orcs from above. First we have the `Request` class:
```java ```java
public class Request { public class Request {
@ -67,7 +66,7 @@ public enum RequestType {
} }
``` ```
Next, we show the request handler hierarchy. Then the request handler hierarchy
```java ```java
@Slf4j @Slf4j
@ -117,7 +116,7 @@ public class OrcCommander extends RequestHandler {
``` ```
The Orc King gives the orders and forms the chain. Then we have the Orc King who gives the orders and forms the chain
```java ```java
public class OrcKing { public class OrcKing {
@ -137,26 +136,18 @@ public class OrcKing {
} }
``` ```
The chain of responsibility in action. Then it is used as follows
```java ```java
var king = new OrcKing(); var king = new OrcKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle"
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner"
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax"
```
The console output.
```
Orc commander handling request "defend castle"
Orc officer handling request "torture prisoner"
Orc soldier handling request "collect tax"
``` ```
## Class diagram ## Class diagram
![alt text](./etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") ![alt text](./etc/chain.urm.png "Chain of Responsibility class diagram")
## Applicability ## Applicability
@ -166,7 +157,7 @@ Use Chain of Responsibility when
* You want to issue a request to one of several objects without specifying the receiver explicitly. * You want to issue a request to one of several objects without specifying the receiver explicitly.
* The set of objects that can handle a request should be specified dynamically. * The set of objects that can handle a request should be specified dynamically.
## Known uses ## Real world examples
* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) * [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29)
* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) * [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html)

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -31,7 +31,7 @@
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.25.0-SNAPSHOT</version> <version>1.25.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>chain-of-responsibility</artifactId> <artifactId>chain</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>

View File

@ -19,7 +19,7 @@ Encapsulate a request as an object, thereby letting you parameterize clients wit
requests, queue or log requests, and support undoable operations. requests, queue or log requests, and support undoable operations.
## Explanation ## Explanation
Real-world example Real world example
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. > There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses > The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
@ -135,7 +135,7 @@ public class Goblin extends Target {
} }
``` ```
Finally, we have the wizard in the main function casting spells. Finally we have the wizard in main function who casts spell
```java ```java
public static void main(String[] args) { public static void main(String[] args) {
@ -202,29 +202,32 @@ Use the Command pattern when you want to:
* Parameterize objects by an action to perform. You can express such parameterization in a * Parameterize objects by an action to perform. You can express such parameterization in a
procedural language with a callback function, that is, a function that's registered somewhere to be procedural language with a callback function, that is, a function that's registered somewhere to be
called at a later point. Commands are an object-oriented replacement for callbacks. called at a later point. Commands are an object-oriented replacement for callbacks.
* Specify, queue, and execute requests at different times. A Command object can have a life * Specify, queue, and execute requests at different times. A Command object can have a lifetime
independent of the original request. If the receiver of a request can be represented in an address independent of the original request. If the receiver of a request can be represented in an address
space-independent way, then you can transfer a command object for the request to a different process space-independent way, then you can transfer a command object for the request to a different process
and fulfill the request there. and fulfill the request there.
* Support undo. The Command's execute operation can store state for reversing its effects in the * Support undo. The Command's execute operation can store state for reversing its effects in the
command itself. The Command interface must have an added un-execute operation that reverses the command itself. The Command interface must have an added un-execute operation that reverses the
effects of a previous call to execute. The executed commands are stored in a history list. effects of a previous call to execute. The executed commands are stored in a history list.
Unlimited-level undo and redo functionality is achieved by traversing this list backward and forward Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
calling un-execute and execute, respectively. un-execute and execute, respectively.
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the * Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
Command interface with load and store operations, you can keep a persistent log of changes. Command interface with load and store operations, you can keep a persistent log of changes.
Recovering from a crash involves reloading logged commands from the disk and re-executing them with Recovering from a crash involves reloading logged commands from disk and re-executing them with
the execute operation. the execute operation.
* Structure a system around high-level operations build on primitive operations. Such a structure is * Structure a system around high-level operations build on primitive operations. Such a structure is
common in information systems that support transactions. A transaction encapsulates a set of data common in information systems that support transactions. A transaction encapsulates a set of changes
changes. The Command pattern offers a way to model transactions. Commands have a common interface, to data. The Command pattern offers a way to model transactions. Commands have a common interface,
letting you invoke all transactions the same way. The pattern also makes it easy to extend the letting you invoke all transactions the same way. The pattern also makes it easy to extend the
system with new transactions. system with new transactions.
* Keep a history of requests.
* Implement callback functionality.
* Implement the undo functionality.
## Known uses ## Typical Use Case
* To keep a history of requests
* Implement callback functionality
* Implement the undo functionality
## Real world examples
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) * [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java)

View File

@ -26,7 +26,7 @@ package com.iluwatar.command;
/** /**
* The Command pattern is a behavioral design pattern in which an object is used to encapsulate all * The Command pattern is a behavioral design pattern in which an object is used to encapsulate all
* information needed to perform an action or trigger an event at a later time. This information * information needed to perform an action or trigger an event at a later time. This information
* includes the method name, the object that owns the method, and values for the method parameters. * includes the method name, the object that owns the method and values for the method parameters.
* *
* <p>Four terms always associated with the command pattern are command, receiver, invoker and * <p>Four terms always associated with the command pattern are command, receiver, invoker and
* client. A command object (spell) knows about the receiver (target) and invokes a method of the * client. A command object (spell) knows about the receiver (target) and invokes a method of the

View File

@ -25,4 +25,4 @@ We need a mechanism in place which can handle these kinds of situations. We have
## Credits ## Credits
* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) * [https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/]

View File

@ -16,15 +16,15 @@ treat individual objects and compositions of objects uniformly.
## Explanation ## Explanation
Real-world example Real world example
> Every sentence is composed of words which are in turn composed of characters. Each of these > Every sentence is composed of words which are in turn composed of characters. Each of these
> objects are printable and they can have something printed before or after them like sentence > objects is printable and they can have something printed before or after them like sentence always
> always ends with full stop and word always has space before it. > ends with full stop and word always has space before it.
In plain words In plain words
> Composite pattern lets clients uniformly treat the individual objects. > Composite pattern lets clients treat the individual objects in a uniform manner.
Wikipedia says Wikipedia says
@ -154,22 +154,10 @@ public class Messenger {
And then it can be used as: And then it can be used as:
```java ```java
var messenger = new Messenger(); var orcMessage = new Messenger().messageFromOrcs();
orcMessage.print(); // Where there is a whip there is a way.
LOGGER.info("Message from the orcs: "); var elfMessage = new Messenger().messageFromElves();
messenger.messageFromOrcs().print(); elfMessage.print(); // Much wind pours from your mouth.
LOGGER.info("Message from the elves: ");
messenger.messageFromElves().print();
```
The console output:
```
Message from the orcs:
Where there is a whip there is a way.
Message from the elves:
Much wind pours from your mouth.
``` ```
## Class diagram ## Class diagram
@ -184,7 +172,7 @@ Use the Composite pattern when
* You want clients to be able to ignore the difference between compositions of objects and * You want clients to be able to ignore the difference between compositions of objects and
individual objects. Clients will treat all objects in the composite structure uniformly. individual objects. Clients will treat all objects in the composite structure uniformly.
## Known uses ## Real world examples
* [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) and [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) * [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) and [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html)
* [Apache Wicket](https://github.com/apache/wicket) component tree, see [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) and [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) * [Apache Wicket](https://github.com/apache/wicket) component tree, see [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) and [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java)

View File

@ -45,13 +45,14 @@ public class App {
* @param args command line args * @param args command line args
*/ */
public static void main(String[] args) { public static void main(String[] args) {
var messenger = new Messenger();
LOGGER.info("Message from the orcs: "); LOGGER.info("Message from the orcs: ");
messenger.messageFromOrcs().print();
LOGGER.info("Message from the elves: "); var orcMessage = new Messenger().messageFromOrcs();
messenger.messageFromElves().print(); orcMessage.print();
LOGGER.info("\nMessage from the elves: ");
var elfMessage = new Messenger().messageFromElves();
elfMessage.print();
} }
} }

View File

@ -39,6 +39,6 @@ public class Sentence extends LetterComposite {
@Override @Override
protected void printThisAfter() { protected void printThisAfter() {
System.out.print(".\n"); System.out.print(".");
} }
} }

View File

@ -3,6 +3,7 @@ layout: pattern
title: Data Bus title: Data Bus
folder: data-bus folder: data-bus
permalink: /patterns/data-bus/ permalink: /patterns/data-bus/
categories: Architectural categories: Architectural
language: en language: en
tags: tags:

View File

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

View File

@ -6,7 +6,7 @@ permalink: /patterns/decorator/
categories: Structural categories: Structural
language: en language: en
tags: tags:
- Gang of Four - Gang Of Four
- Extensibility - Extensibility
--- ---
@ -21,9 +21,9 @@ alternative to subclassing for extending functionality.
## Explanation ## Explanation
Real-world example Real world example
> There is an angry troll living in the nearby hills. Usually, it goes bare-handed but sometimes it > There is an angry troll living in the nearby hills. Usually it goes bare handed but sometimes it
> has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it > has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it
> dynamically with a suitable weapon. > dynamically with a suitable weapon.
@ -72,7 +72,7 @@ public class SimpleTroll implements Troll {
} }
``` ```
Next, we want to add a club for the troll. We can do it dynamically by using a decorator: Next we want to add club for the troll. We can do it dynamically by using a decorator:
```java ```java
@Slf4j @Slf4j
@ -106,33 +106,23 @@ Here's the troll in action:
```java ```java
// simple troll // simple troll
LOGGER.info("A simple looking troll approaches.");
var troll = new SimpleTroll(); var troll = new SimpleTroll();
troll.attack(); troll.attack(); // The troll tries to grab you!
troll.fleeBattle(); troll.fleeBattle(); // The troll shrieks in horror and runs away!
LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower());
// change the behavior of the simple troll by adding a decorator // change the behavior of the simple troll by adding a decorator
LOGGER.info("A troll with huge club surprises you.");
var clubbedTroll = new ClubbedTroll(troll); var clubbedTroll = new ClubbedTroll(troll);
clubbedTroll.attack(); clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you with a club!
clubbedTroll.fleeBattle(); clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away!
LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower());
``` ```
Program output: Program output:
```java ```java
A simple looking troll approaches.
The troll tries to grab you! The troll tries to grab you!
The troll shrieks in horror and runs away! The troll shrieks in horror and runs away!
Simple troll power: 10. The troll tries to grab you! The troll swings at you with a club!
A troll with huge club surprises you.
The troll tries to grab you!
The troll swings at you with a club!
The troll shrieks in horror and runs away! The troll shrieks in horror and runs away!
Clubbed troll power: 20.
``` ```
## Class diagram ## Class diagram
@ -150,11 +140,11 @@ affecting other objects.
are possible and would produce an explosion of subclasses to support every combination. Or a class are possible and would produce an explosion of subclasses to support every combination. Or a class
definition may be hidden or otherwise unavailable for subclassing. definition may be hidden or otherwise unavailable for subclassing.
## Tutorials ## Tutorial
* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) * [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example)
## Known uses ## Real world examples
* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html),
[java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html)

View File

@ -50,13 +50,13 @@ public class App {
var troll = new SimpleTroll(); var troll = new SimpleTroll();
troll.attack(); troll.attack();
troll.fleeBattle(); troll.fleeBattle();
LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower()); LOGGER.info("Simple troll power {}.\n", troll.getAttackPower());
// change the behavior of the simple troll by adding a decorator // change the behavior of the simple troll by adding a decorator
LOGGER.info("A troll with huge club surprises you."); LOGGER.info("A troll with huge club surprises you.");
var clubbedTroll = new ClubbedTroll(troll); var clubbedTroll = new ClubbedTroll(troll);
clubbedTroll.attack(); clubbedTroll.attack();
clubbedTroll.fleeBattle(); clubbedTroll.fleeBattle();
LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower()); LOGGER.info("Clubbed troll power {}.\n", clubbedTroll.getAttackPower());
} }
} }

View File

@ -103,4 +103,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) * [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) * [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) * [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: 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) * [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)

View File

@ -1,326 +0,0 @@
---
layout: pattern
title: Domain Model
folder: domain-model
permalink: /patterns/domain-model/
categories: 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
> Let's assume that we need to build an e-commerce web application. While analyzing requirements you will notice that there are few nouns you talk about repeatedly. Its your Customer, and a Product the customer looks for. These two are your domain-specific classes and each of that classes will include some business logic specific to its domain.
In plain words
> The Domain Model is an object model of the domain that incorporates both behavior and data.
Programmatic Example
In the example of the e-commerce app, we need to deal with the domain logic of customers who want to buy products and return them if they want. We can use the domain model pattern and create classes `Customer` and `Product` where every single instance of that class incorporates both behavior and data and represents only one record in the underlying table.
Here is the `Product` domain class with fields `name`, `price`, `expirationDate` which is specific for each product, `productDao` for working with DB, `save` method for saving product and `getSalePrice` method which return price for this product with discount.
```java
@Slf4j
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Product {
private static final int DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE = 4;
private static final double DISCOUNT_RATE = 0.2;
@NonNull private final ProductDao productDao;
@NonNull private String name;
@NonNull private Money price;
@NonNull private LocalDate expirationDate;
/**
* Save product or update if product already exist.
*/
public void save() {
try {
Optional<Product> product = productDao.findByName(name);
if (product.isPresent()) {
productDao.update(this);
} else {
productDao.save(this);
}
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Calculate sale price of product with discount.
*/
public Money getSalePrice() {
return price.minus(calculateDiscount());
}
private Money calculateDiscount() {
if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate)
< DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) {
return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN);
}
return Money.zero(USD);
}
}
```
Here is the `Customer` domain class with fields `name`, `money` which is specific for each customer, `customerDao` for working with DB, `save` for saving customer, `buyProduct` which add a product to purchases and withdraw money, `returnProduct` which remove product from purchases and return money, `showPurchases` and `showBalance` methods for printing customer's purchases and money balance.
```java
@Slf4j
@Getter
@Setter
@Builder
public class Customer {
@NonNull private final CustomerDao customerDao;
@Builder.Default private List<Product> purchases = new ArrayList<>();
@NonNull private String name;
@NonNull private Money money;
/**
* Save customer or update if customer already exist.
*/
public void save() {
try {
Optional<Customer> customer = customerDao.findByName(name);
if (customer.isPresent()) {
customerDao.update(this);
} else {
customerDao.save(this);
}
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Add product to purchases, save to db and withdraw money.
*
* @param product to buy.
*/
public void buyProduct(Product product) {
LOGGER.info(
String.format(
"%s want to buy %s($%.2f)...",
name, product.getName(), product.getSalePrice().getAmount()));
try {
withdraw(product.getSalePrice());
} catch (IllegalArgumentException ex) {
LOGGER.error(ex.getMessage());
return;
}
try {
customerDao.addProduct(product, this);
purchases.add(product);
LOGGER.info(String.format("%s bought %s!", name, product.getName()));
} catch (SQLException exception) {
receiveMoney(product.getSalePrice());
LOGGER.error(exception.getMessage());
}
}
/**
* Remove product from purchases, delete from db and return money.
*
* @param product to return.
*/
public void returnProduct(Product product) {
LOGGER.info(
String.format(
"%s want to return %s($%.2f)...",
name, product.getName(), product.getSalePrice().getAmount()));
if (purchases.contains(product)) {
try {
customerDao.deleteProduct(product, this);
purchases.remove(product);
receiveMoney(product.getSalePrice());
LOGGER.info(String.format("%s returned %s!", name, product.getName()));
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
} else {
LOGGER.error(String.format("%s didn't buy %s...", name, product.getName()));
}
}
/**
* Print customer's purchases.
*/
public void showPurchases() {
Optional<String> purchasesToShow =
purchases.stream()
.map(p -> p.getName() + " - $" + p.getSalePrice().getAmount())
.reduce((p1, p2) -> p1 + ", " + p2);
if (purchasesToShow.isPresent()) {
LOGGER.info(name + " bought: " + purchasesToShow.get());
} else {
LOGGER.info(name + " didn't bought anything");
}
}
/**
* Print customer's money balance.
*/
public void showBalance() {
LOGGER.info(name + " balance: " + money);
}
private void withdraw(Money amount) throws IllegalArgumentException {
if (money.compareTo(amount) < 0) {
throw new IllegalArgumentException("Not enough money!");
}
money = money.minus(amount);
}
private void receiveMoney(Money amount) {
money = money.plus(amount);
}
}
```
In the class `App`, we create a new instance of class Customer which represents customer Tom and handle data and actions of that customer and creating three products that Tom wants to buy.
```java
// Create data source and create the customers, products and purchases tables
final var dataSource = createDataSource();
deleteSchema(dataSource);
createSchema(dataSource);
// create customer
var customerDao = new CustomerDaoImpl(dataSource);
var tom =
Customer.builder()
.name("Tom")
.money(Money.of(USD, 30))
.customerDao(customerDao)
.build();
tom.save();
// create products
var productDao = new ProductDaoImpl(dataSource);
var eggs =
Product.builder()
.name("Eggs")
.price(Money.of(USD, 10.0))
.expirationDate(LocalDate.now().plusDays(7))
.productDao(productDao)
.build();
var butter =
Product.builder()
.name("Butter")
.price(Money.of(USD, 20.00))
.expirationDate(LocalDate.now().plusDays(9))
.productDao(productDao)
.build();
var cheese =
Product.builder()
.name("Cheese")
.price(Money.of(USD, 25.0))
.expirationDate(LocalDate.now().plusDays(2))
.productDao(productDao)
.build();
eggs.save();
butter.save();
cheese.save();
// show money balance of customer after each purchase
tom.showBalance();
tom.showPurchases();
// buy eggs
tom.buyProduct(eggs);
tom.showBalance();
// buy butter
tom.buyProduct(butter);
tom.showBalance();
// trying to buy cheese, but receive a refusal
// because he didn't have enough money
tom.buyProduct(cheese);
tom.showBalance();
// return butter and get money back
tom.returnProduct(butter);
tom.showBalance();
// Tom can buy cheese now because he has enough money
// and there is a discount on cheese because it expires in 2 days
tom.buyProduct(cheese);
tom.save();
// show money balance and purchases after shopping
tom.showBalance();
tom.showPurchases();
```
The program output:
```java
17:52:28.690 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 30.00
17:52:28.695 [main] INFO com.iluwatar.domainmodel.Customer - Tom didn't bought anything
17:52:28.699 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Eggs($10.00)...
17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Eggs!
17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 20.00
17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Butter($20.00)...
17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Butter!
17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00
17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Cheese($20.00)...
17:52:28.712 [main] ERROR com.iluwatar.domainmodel.Customer - Not enough money!
17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00
17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to return Butter($20.00)...
17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom returned Butter!
17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 20.00
17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Cheese($20.00)...
17:52:28.726 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Cheese!
17:52:28.737 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00
17:52:28.738 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought: Eggs - $10.00, Cheese - $20.00
```
## Class diagram
![](./etc/domain-model.urm.png "domain model")
## Applicability
Use a Domain model pattern when your domain logic is complex and that complexity can rapidly grow because this pattern handles increasing complexity very well. Otherwise, it's a more complex solution for organizing domain logic, so shouldn't use Domain Model pattern for systems with simple domain logic, because the cost of understanding it and complexity of data source exceeds the benefit of this pattern.
## Related patterns
- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/)
- [Table Module](https://java-design-patterns.com/patterns/table-module/)
- [Service Layer](https://java-design-patterns.com/patterns/service-layer/)
## Credits
* [Domain Model Pattern](https://martinfowler.com/eaaCatalog/domainModel.html)
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04)
* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

View File

@ -1,87 +0,0 @@
@startuml
package com.iluwatar.domainmodel {
class App {
+ CREATE_SCHEMA_SQL : String {static}
+ DELETE_SCHEMA_SQL : String {static}
+ H2_DB_URL : String {static}
+ App()
- createDataSource() : DataSource {static}
- createSchema(dataSource : DataSource) {static}
- deleteSchema(dataSource : DataSource) {static}
+ main(args : String[]) {static}
}
class Customer {
- customerDao : CustomerDao
- money : Money
- name : String
- purchases : List<Product>
~ Customer(customerDao : CustomerDao, purchases : List<Product>, name : String, money : Money)
+ builder() : CustomerBuilder {static}
+ buyProduct(product : Product)
+ getCustomerDao() : CustomerDao
+ getMoney() : Money
+ getName() : String
+ getPurchases() : List<Product>
- receiveMoney(amount : Money)
+ returnProduct(product : Product)
+ save()
+ setMoney(money : Money)
+ setName(name : String)
+ setPurchases(purchases : List<Product>)
+ showBalance()
+ showPurchases()
- withdraw(amount : Money)
}
interface CustomerDao {
+ addProduct(Product, Customer) {abstract}
+ deleteProduct(Product, Customer) {abstract}
+ findByName(String) : Optional<Customer> {abstract}
+ save(Customer) {abstract}
+ update(Customer) {abstract}
}
class CustomerDaoImpl {
- dataSource : DataSource
+ CustomerDaoImpl(userDataSource : DataSource)
+ addProduct(product : Product, customer : Customer)
+ deleteProduct(product : Product, customer : Customer)
+ findByName(name : String) : Optional<Customer>
+ save(customer : Customer)
+ update(customer : Customer)
}
class Product {
- expirationDate : LocalDate
- name : String
- price : Money
- productDao : ProductDao
+ Product(productDao : ProductDao, name : String, price : Money, expirationDate : LocalDate)
+ builder() : ProductBuilder {static}
- calculateDiscount() : Money
+ getExpirationDate() : LocalDate
+ getName() : String
+ getPrice() : Money
+ getProductDao() : ProductDao
+ getSalePrice() : Money
+ save()
+ setExpirationDate(expirationDate : LocalDate)
+ setName(name : String)
+ setPrice(price : Money)
}
interface ProductDao {
+ findByName(String) : Optional<Product> {abstract}
+ save(Product) {abstract}
+ update(Product) {abstract}
}
class ProductDaoImpl {
- dataSource : DataSource
+ ProductDaoImpl(userDataSource : DataSource)
+ findByName(name : String) : Optional<Product>
+ save(product : Product)
+ update(product : Product)
}
}
Product --> ProductDao
Customer --> CustomerDao
Customer --> Product
CustomerDaoImpl ..|> CustomerDao
ProductDaoImpl ..|> ProductDao
@enduml

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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>domain-model</artifactId>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.domainmodel.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,173 +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.domainmodel;
import static org.joda.money.CurrencyUnit.USD;
import java.sql.SQLException;
import java.time.LocalDate;
import javax.sql.DataSource;
import org.h2.jdbcx.JdbcDataSource;
import org.joda.money.Money;
/**
* Domain Model pattern is a more complex solution for organizing domain logic than Transaction
* Script and Table Module. It provides an object-oriented way of dealing with complicated logic.
* Instead of having one procedure that handles all business logic for a user action like
* Transaction Script there are multiple objects and each of them handles a slice of domain logic
* that is relevant to it. The significant difference between Domain Model and Table Module pattern
* is that in Table Module a single class encapsulates all the domain logic for all records stored
* in table when in Domain Model every single class represents only one record in underlying table.
*
* <p>In this example, we will use the Domain Model pattern to implement buying of products
* by customers in a Shop. The main method will create a customer and a few products.
* Customer will do a few purchases, try to buy product which are too expensive for him,
* return product which he bought to return money.</p>
*/
public class App {
public static final String H2_DB_URL = "jdbc:h2:~/test";
public static final String CREATE_SCHEMA_SQL =
"CREATE TABLE CUSTOMERS (name varchar primary key, money decimal);"
+ "CREATE TABLE PRODUCTS (name varchar primary key, price decimal, expiration_date date);"
+ "CREATE TABLE PURCHASES ("
+ "product_name varchar references PRODUCTS(name),"
+ "customer_name varchar references CUSTOMERS(name));";
public static final String DELETE_SCHEMA_SQL =
"DROP TABLE CUSTOMERS IF EXISTS;"
+ "DROP TABLE PURCHASES IF EXISTS;"
+ "DROP TABLE PRODUCTS IF EXISTS;";
/**
* Program entry point.
*
* @param args command line arguments
* @throws Exception if any error occurs
*/
public static void main(String[] args) throws Exception {
// Create data source and create the customers, products and purchases tables
final var dataSource = createDataSource();
deleteSchema(dataSource);
createSchema(dataSource);
// create customer
var customerDao = new CustomerDaoImpl(dataSource);
var tom =
Customer.builder()
.name("Tom")
.money(Money.of(USD, 30))
.customerDao(customerDao)
.build();
tom.save();
// create products
var productDao = new ProductDaoImpl(dataSource);
var eggs =
Product.builder()
.name("Eggs")
.price(Money.of(USD, 10.0))
.expirationDate(LocalDate.now().plusDays(7))
.productDao(productDao)
.build();
var butter =
Product.builder()
.name("Butter")
.price(Money.of(USD, 20.00))
.expirationDate(LocalDate.now().plusDays(9))
.productDao(productDao)
.build();
var cheese =
Product.builder()
.name("Cheese")
.price(Money.of(USD, 25.0))
.expirationDate(LocalDate.now().plusDays(2))
.productDao(productDao)
.build();
eggs.save();
butter.save();
cheese.save();
// show money balance of customer after each purchase
tom.showBalance();
tom.showPurchases();
// buy eggs
tom.buyProduct(eggs);
tom.showBalance();
// buy butter
tom.buyProduct(butter);
tom.showBalance();
// trying to buy cheese, but receive a refusal
// because he didn't have enough money
tom.buyProduct(cheese);
tom.showBalance();
// return butter and get money back
tom.returnProduct(butter);
tom.showBalance();
// Tom can buy cheese now because he has enough money
// and there is a discount on cheese because it expires in 2 days
tom.buyProduct(cheese);
tom.save();
// show money balance and purchases after shopping
tom.showBalance();
tom.showPurchases();
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setUrl(H2_DB_URL);
return dataSource;
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
}
}
}

View File

@ -1,153 +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.domainmodel;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.joda.money.Money;
/**
* This class organizes domain logic of customer.
* A single instance of this class
* contains both the data and behavior of a single customer.
*/
@Slf4j
@Getter
@Setter
@Builder
public class Customer {
@NonNull private final CustomerDao customerDao;
@Builder.Default private List<Product> purchases = new ArrayList<>();
@NonNull private String name;
@NonNull private Money money;
/**
* Save customer or update if customer already exist.
*/
public void save() {
try {
Optional<Customer> customer = customerDao.findByName(name);
if (customer.isPresent()) {
customerDao.update(this);
} else {
customerDao.save(this);
}
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Add product to purchases, save to db and withdraw money.
*
* @param product to buy.
*/
public void buyProduct(Product product) {
LOGGER.info(
String.format(
"%s want to buy %s($%.2f)...",
name, product.getName(), product.getSalePrice().getAmount()));
try {
withdraw(product.getSalePrice());
} catch (IllegalArgumentException ex) {
LOGGER.error(ex.getMessage());
return;
}
try {
customerDao.addProduct(product, this);
purchases.add(product);
LOGGER.info(String.format("%s bought %s!", name, product.getName()));
} catch (SQLException exception) {
receiveMoney(product.getSalePrice());
LOGGER.error(exception.getMessage());
}
}
/**
* Remove product from purchases, delete from db and return money.
*
* @param product to return.
*/
public void returnProduct(Product product) {
LOGGER.info(
String.format(
"%s want to return %s($%.2f)...",
name, product.getName(), product.getSalePrice().getAmount()));
if (purchases.contains(product)) {
try {
customerDao.deleteProduct(product, this);
purchases.remove(product);
receiveMoney(product.getSalePrice());
LOGGER.info(String.format("%s returned %s!", name, product.getName()));
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
} else {
LOGGER.error(String.format("%s didn't buy %s...", name, product.getName()));
}
}
/**
* Print customer's purchases.
*/
public void showPurchases() {
Optional<String> purchasesToShow =
purchases.stream()
.map(p -> p.getName() + " - $" + p.getSalePrice().getAmount())
.reduce((p1, p2) -> p1 + ", " + p2);
if (purchasesToShow.isPresent()) {
LOGGER.info(name + " bought: " + purchasesToShow.get());
} else {
LOGGER.info(name + " didn't bought anything");
}
}
/**
* Print customer's money balance.
*/
public void showBalance() {
LOGGER.info(name + " balance: " + money);
}
private void withdraw(Money amount) throws IllegalArgumentException {
if (money.compareTo(amount) < 0) {
throw new IllegalArgumentException("Not enough money!");
}
money = money.minus(amount);
}
private void receiveMoney(Money amount) {
money = money.plus(amount);
}
}

View File

@ -1,40 +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.domainmodel;
import java.sql.SQLException;
import java.util.Optional;
public interface CustomerDao {
Optional<Customer> findByName(String name) throws SQLException;
void update(Customer customer) throws SQLException;
void save(Customer customer) throws SQLException;
void addProduct(Product product, Customer customer) throws SQLException;
void deleteProduct(Product product, Customer customer) throws SQLException;
}

View File

@ -1,109 +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.domainmodel;
import static org.joda.money.CurrencyUnit.USD;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import javax.sql.DataSource;
import org.joda.money.Money;
public class CustomerDaoImpl implements CustomerDao {
private final DataSource dataSource;
public CustomerDaoImpl(final DataSource userDataSource) {
this.dataSource = userDataSource;
}
@Override
public Optional<Customer> findByName(String name) throws SQLException {
var sql = "select * from CUSTOMERS where name = ?;";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, name);
ResultSet rs = preparedStatement.executeQuery();
if (rs.next()) {
return Optional.of(
Customer.builder()
.name(rs.getString("name"))
.money(Money.of(USD, rs.getBigDecimal("money")))
.customerDao(this)
.build());
} else {
return Optional.empty();
}
}
}
@Override
public void update(Customer customer) throws SQLException {
var sql = "update CUSTOMERS set money = ? where name = ?;";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setBigDecimal(1, customer.getMoney().getAmount());
preparedStatement.setString(2, customer.getName());
preparedStatement.executeUpdate();
}
}
@Override
public void save(Customer customer) throws SQLException {
var sql = "insert into CUSTOMERS (name, money) values (?, ?)";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, customer.getName());
preparedStatement.setBigDecimal(2, customer.getMoney().getAmount());
preparedStatement.executeUpdate();
}
}
@Override
public void addProduct(Product product, Customer customer) throws SQLException {
var sql = "insert into PURCHASES (product_name, customer_name) values (?,?)";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, product.getName());
preparedStatement.setString(2, customer.getName());
preparedStatement.executeUpdate();
}
}
@Override
public void deleteProduct(Product product, Customer customer) throws SQLException {
var sql = "delete from PURCHASES where product_name = ? and customer_name = ?";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, product.getName());
preparedStatement.setString(2, customer.getName());
preparedStatement.executeUpdate();
}
}
}

View File

@ -1,94 +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.domainmodel;
import static org.joda.money.CurrencyUnit.USD;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.joda.money.Money;
/**
* This class organizes domain logic of product.
* A single instance of this class
* contains both the data and behavior of a single product.
*/
@Slf4j
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Product {
private static final int DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE = 4;
private static final double DISCOUNT_RATE = 0.2;
@NonNull private final ProductDao productDao;
@NonNull private String name;
@NonNull private Money price;
@NonNull private LocalDate expirationDate;
/**
* Save product or update if product already exist.
*/
public void save() {
try {
Optional<Product> product = productDao.findByName(name);
if (product.isPresent()) {
productDao.update(this);
} else {
productDao.save(this);
}
} catch (SQLException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Calculate sale price of product with discount.
*/
public Money getSalePrice() {
return price.minus(calculateDiscount());
}
private Money calculateDiscount() {
if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate)
< DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) {
return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN);
}
return Money.zero(USD);
}
}

View File

@ -1,36 +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.domainmodel;
import java.sql.SQLException;
import java.util.Optional;
public interface ProductDao {
Optional<Product> findByName(String name) throws SQLException;
void save(Product product) throws SQLException;
void update(Product product) throws SQLException;
}

View File

@ -1,92 +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.domainmodel;
import static org.joda.money.CurrencyUnit.USD;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import javax.sql.DataSource;
import org.joda.money.Money;
public class ProductDaoImpl implements ProductDao {
private final DataSource dataSource;
public ProductDaoImpl(final DataSource userDataSource) {
this.dataSource = userDataSource;
}
@Override
public Optional<Product> findByName(String name) throws SQLException {
var sql = "select * from PRODUCTS where name = ?;";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, name);
ResultSet rs = preparedStatement.executeQuery();
if (rs.next()) {
return Optional.of(
Product.builder()
.name(rs.getString("name"))
.price(Money.of(USD, rs.getBigDecimal("price")))
.expirationDate(rs.getDate("expiration_date").toLocalDate())
.productDao(this)
.build());
} else {
return Optional.empty();
}
}
}
@Override
public void save(Product product) throws SQLException {
var sql = "insert into PRODUCTS (name, price, expiration_date) values (?, ?, ?)";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, product.getName());
preparedStatement.setBigDecimal(2, product.getPrice().getAmount());
preparedStatement.setDate(3, Date.valueOf(product.getExpirationDate()));
preparedStatement.executeUpdate();
}
}
@Override
public void update(Product product) throws SQLException {
var sql = "update PRODUCTS set price = ?, expiration_date = ? where name = ?;";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setBigDecimal(1, product.getPrice().getAmount());
preparedStatement.setDate(2, Date.valueOf(product.getExpirationDate()));
preparedStatement.setString(3, product.getName());
preparedStatement.executeUpdate();
}
}
}

View File

@ -1,38 +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.domainmodel;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** Tests that Domain Model example runs without errors. */
final class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[] {}));
}
}

View File

@ -1,164 +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.domainmodel;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.*;
class CustomerDaoImplTest {
public static final String INSERT_CUSTOMER_SQL = "insert into CUSTOMERS values('customer', 100)";
public static final String SELECT_CUSTOMERS_SQL = "select name, money from CUSTOMERS";
public static final String INSERT_PURCHASES_SQL =
"insert into PURCHASES values('product', 'customer')";
public static final String SELECT_PURCHASES_SQL =
"select product_name, customer_name from PURCHASES";
private DataSource dataSource;
private Product product;
private Customer customer;
private CustomerDao customerDao;
@BeforeEach
void setUp() throws SQLException {
// create db schema
dataSource = TestUtils.createDataSource();
TestUtils.deleteSchema(dataSource);
TestUtils.createSchema(dataSource);
// setup objects
customerDao = new CustomerDaoImpl(dataSource);
customer = Customer.builder().name("customer").money(Money.of(CurrencyUnit.USD,100.0)).customerDao(customerDao).build();
product =
Product.builder()
.name("product")
.price(Money.of(USD, 100.0))
.expirationDate(LocalDate.parse("2021-06-27"))
.productDao(new ProductDaoImpl(dataSource))
.build();
}
@AfterEach
void tearDown() throws SQLException {
TestUtils.deleteSchema(dataSource);
}
@Test
void shouldFindCustomerByName() throws SQLException {
var customer = customerDao.findByName("customer");
assertTrue(customer.isEmpty());
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
customer = customerDao.findByName("customer");
assertTrue(customer.isPresent());
assertEquals("customer", customer.get().getName());
assertEquals(Money.of(USD, 100), customer.get().getMoney());
}
@Test
void shouldSaveCustomer() throws SQLException {
customerDao.save(customer);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_CUSTOMERS_SQL)) {
assertTrue(rs.next());
assertEquals(customer.getName(), rs.getString("name"));
assertEquals(customer.getMoney(), Money.of(USD, rs.getBigDecimal("money")));
}
assertThrows(SQLException.class, () -> customerDao.save(customer));
}
@Test
void shouldUpdateCustomer() throws SQLException {
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
customer.setMoney(Money.of(CurrencyUnit.USD, 99));
customerDao.update(customer);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_CUSTOMERS_SQL)) {
assertTrue(rs.next());
assertEquals(customer.getName(), rs.getString("name"));
assertEquals(customer.getMoney(), Money.of(USD, rs.getBigDecimal("money")));
assertFalse(rs.next());
}
}
@Test
void shouldAddProductToPurchases() throws SQLException {
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
TestUtils.executeSQL(ProductDaoImplTest.INSERT_PRODUCT_SQL, dataSource);
customerDao.addProduct(product, customer);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_PURCHASES_SQL)) {
assertTrue(rs.next());
assertEquals(product.getName(), rs.getString("product_name"));
assertEquals(customer.getName(), rs.getString("customer_name"));
assertFalse(rs.next());
}
}
@Test
void shouldDeleteProductFromPurchases() throws SQLException {
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
TestUtils.executeSQL(ProductDaoImplTest.INSERT_PRODUCT_SQL, dataSource);
TestUtils.executeSQL(INSERT_PURCHASES_SQL, dataSource);
customerDao.deleteProduct(product, customer);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_PURCHASES_SQL)) {
assertFalse(rs.next());
}
}
}

View File

@ -1,112 +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.domainmodel;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
class CustomerTest {
private CustomerDao customerDao;
private Customer customer;
private Product product;
@BeforeEach
void setUp() {
customerDao = mock(CustomerDao.class);
customer = Customer.builder()
.name("customer")
.money(Money.of(CurrencyUnit.USD, 100.0))
.customerDao(customerDao)
.build();
product = Product.builder()
.name("product")
.price(Money.of(USD, 100.0))
.expirationDate(LocalDate.now().plusDays(10))
.productDao(mock(ProductDao.class))
.build();
}
@Test
void shouldSaveCustomer() throws SQLException {
when(customerDao.findByName("customer")).thenReturn(Optional.empty());
customer.save();
verify(customerDao, times(1)).save(customer);
when(customerDao.findByName("customer")).thenReturn(Optional.of(customer));
customer.save();
verify(customerDao, times(1)).update(customer);
}
@Test
void shouldAddProductToPurchases() {
product.setPrice(Money.of(USD, 200.0));
customer.buyProduct(product);
assertEquals(customer.getPurchases(), new ArrayList<>());
assertEquals(customer.getMoney(), Money.of(USD,100));
product.setPrice(Money.of(USD, 100.0));
customer.buyProduct(product);
assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases());
assertEquals(Money.zero(USD), customer.getMoney());
}
@Test
void shouldRemoveProductFromPurchases() {
customer.setPurchases(new ArrayList<>(Arrays.asList(product)));
customer.returnProduct(product);
assertEquals(new ArrayList<>(), customer.getPurchases());
assertEquals(Money.of(USD, 200), customer.getMoney());
customer.returnProduct(product);
assertEquals(new ArrayList<>(), customer.getPurchases());
assertEquals(Money.of(USD, 200), customer.getMoney());
}
}

View File

@ -1,127 +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.domainmodel;
import org.joda.money.Money;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.*;
class ProductDaoImplTest {
public static final String INSERT_PRODUCT_SQL =
"insert into PRODUCTS values('product', 100, DATE '2021-06-27')";
public static final String SELECT_PRODUCTS_SQL =
"select name, price, expiration_date from PRODUCTS";
private DataSource dataSource;
private ProductDao productDao;
private Product product;
@BeforeEach
void setUp() throws SQLException {
// create schema
dataSource = TestUtils.createDataSource();
TestUtils.deleteSchema(dataSource);
TestUtils.createSchema(dataSource);
// setup objects
productDao = new ProductDaoImpl(dataSource);
product =
Product.builder()
.name("product")
.price(Money.of(USD, 100.0))
.expirationDate(LocalDate.parse("2021-06-27"))
.productDao(productDao)
.build();
}
@AfterEach
void tearDown() throws SQLException {
TestUtils.deleteSchema(dataSource);
}
@Test
void shouldFindProductByName() throws SQLException {
var product = productDao.findByName("product");
assertTrue(product.isEmpty());
TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource);
product = productDao.findByName("product");
assertTrue(product.isPresent());
assertEquals("product", product.get().getName());
assertEquals(Money.of(USD, 100), product.get().getPrice());
assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate());
}
@Test
void shouldSaveProduct() throws SQLException {
productDao.save(product);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) {
assertTrue(rs.next());
assertEquals(product.getName(), rs.getString("name"));
assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price")));
assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate());
}
assertThrows(SQLException.class, () -> productDao.save(product));
}
@Test
void shouldUpdateProduct() throws SQLException {
TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource);
product.setPrice(Money.of(USD, 99.0));
productDao.update(product);
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) {
assertTrue(rs.next());
assertEquals(product.getName(), rs.getString("name"));
assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price")));
assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate());
}
}
}

View File

@ -1,78 +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.domainmodel;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Optional;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
class ProductTest {
private ProductDao productDao;
private Product product;
@BeforeEach
void setUp() {
productDao = mock(ProductDaoImpl.class);
product = Product.builder()
.name("product")
.price(Money.of(USD, 100.0))
.expirationDate(LocalDate.now().plusDays(10))
.productDao(productDao)
.build();
}
@Test
void shouldSaveProduct() throws SQLException {
when(productDao.findByName("product")).thenReturn(Optional.empty());
product.save();
verify(productDao, times(1)).save(product);
when(productDao.findByName("product")).thenReturn(Optional.of(product));
product.save();
verify(productDao, times(1)).update(product);
}
@Test
void shouldGetSalePriceOfProduct() {
assertEquals(Money.of(USD, 100), product.getSalePrice());
product.setExpirationDate(LocalDate.now().plusDays(2));
assertEquals(Money.of(USD, 80), product.getSalePrice());
}
}

View File

@ -1,53 +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.domainmodel;
import org.h2.jdbcx.JdbcDataSource;
import javax.sql.DataSource;
import java.sql.SQLException;
public class TestUtils {
public static void executeSQL( String sql, DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.executeUpdate(sql);
}
}
public static void createSchema(DataSource dataSource) throws SQLException {
TestUtils.executeSQL(App.CREATE_SCHEMA_SQL, dataSource);
}
public static void deleteSchema(DataSource dataSource) throws SQLException {
TestUtils.executeSQL(App.DELETE_SCHEMA_SQL, dataSource);
}
public static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(App.H2_DB_URL);
return dataSource;
}
}

View File

@ -24,6 +24,7 @@ Use an Event-driven architecture when
## Real world examples ## 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) * 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) * 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. * MySQL runs triggers based on events such as inserts and update events happening on database tables.
@ -31,6 +32,6 @@ Use an Event-driven architecture when
## Credits ## Credits
* [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture) * [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture)
* [What is an Event-Driven Architecture](https://aws.amazon.com/event-driven-architecture/) * [Fundamental Components of an Event-Driven Architecture](http://giocc.com/fundamental-components-of-an-event-driven-architecture.html)
* [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications) * [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) * [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture)

View File

@ -17,7 +17,7 @@ interface that makes the subsystem easier to use.
## Explanation ## Explanation
Real-world example Real world example
> How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you > How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you
> believe because you are using a simple interface that goldmine provides on the outside, internally > believe because you are using a simple interface that goldmine provides on the outside, internally

View File

@ -7,7 +7,7 @@ categories: Creational
language: en language: en
tags: tags:
- Extensibility - Extensibility
- Gang of Four - Gang Of Four
--- ---
## Also known as ## Also known as
@ -21,7 +21,7 @@ Factory Method lets a class defer instantiation to subclasses.
## Explanation ## Explanation
Real-world example Real world example
> Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons. > Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons.
> Depending on the customer at hand the right type of blacksmith is summoned. > Depending on the customer at hand the right type of blacksmith is summoned.
@ -40,7 +40,7 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
Taking our blacksmith example above. First of all, we have a `Blacksmith` interface and some Taking our blacksmith example above. First of all we have a `Blacksmith` interface and some
implementations for it: implementations for it:
```java ```java
@ -65,25 +65,15 @@ When the customers come, the correct type of blacksmith is summoned and requeste
manufactured: manufactured:
```java ```java
Blacksmith blacksmith = new OrcBlacksmith(); var blacksmith = new ElfBlacksmith();
Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); blacksmith.manufactureWeapon(WeaponType.SPEAR);
LOGGER.info("{} manufactured {}", blacksmith, weapon); blacksmith.manufactureWeapon(WeaponType.AXE);
weapon = blacksmith.manufactureWeapon(WeaponType.AXE);
LOGGER.info("{} manufactured {}", blacksmith, weapon);
blacksmith = new ElfBlacksmith();
weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR);
LOGGER.info("{} manufactured {}", blacksmith, weapon);
weapon = blacksmith.manufactureWeapon(WeaponType.AXE);
LOGGER.info("{} manufactured {}", blacksmith, weapon);
``` ```
Program output: Program output:
``` ```java
The orc blacksmith manufactured an orcish spear // Elven spear
The orc blacksmith manufactured an orcish axe // Elven axe
The elf blacksmith manufactured an elven spear
The elf blacksmith manufactured an elven axe
``` ```
## Class diagram ## Class diagram
@ -99,7 +89,7 @@ Use the Factory Method pattern when:
* Classes delegate responsibility to one of several helper subclasses, and you want to localize the * Classes delegate responsibility to one of several helper subclasses, and you want to localize the
knowledge of which helper subclass is the delegate. knowledge of which helper subclass is the delegate.
## Known uses ## Real world examples
* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) * [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) * [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)

View File

@ -26,7 +26,7 @@ package com.iluwatar.factory.method;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* The Factory Method is a creational design pattern that uses factory methods to deal with the * The Factory Method is a creational design pattern which uses factory methods to deal with the
* problem of creating objects without specifying the exact class of object that will be created. * problem of creating objects without specifying the exact class of object that will be created.
* This is done by creating objects via calling a factory method either specified in an interface * This is done by creating objects via calling a factory method either specified in an interface
* and implemented by child classes, or implemented in a base class and optionally overridden by * and implemented by child classes, or implemented in a base class and optionally overridden by
@ -41,22 +41,39 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class App { public class App {
private final Blacksmith blacksmith;
/**
* Creates an instance of <code>App</code> which will use <code>blacksmith</code> to manufacture
* the weapons for war.
* <code>App</code> is unaware which concrete implementation of {@link Blacksmith} it is using.
* The decision of which blacksmith implementation to use may depend on configuration, or
* the type of rival in war.
* @param blacksmith a non-null implementation of blacksmith
*/
public App(Blacksmith blacksmith) {
this.blacksmith = blacksmith;
}
/** /**
* Program entry point. * Program entry point.
*
* @param args command line args * @param args command line args
*/ */
public static void main(String[] args) { public static void main(String[] args) {
// Lets go to war with Orc weapons
var app = new App(new OrcBlacksmith());
app.manufactureWeapons();
Blacksmith blacksmith = new OrcBlacksmith(); // Lets go to war with Elf weapons
Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); app = new App(new ElfBlacksmith());
LOGGER.info("{} manufactured {}", blacksmith, weapon); app.manufactureWeapons();
weapon = blacksmith.manufactureWeapon(WeaponType.AXE); }
LOGGER.info("{} manufactured {}", blacksmith, weapon);
blacksmith = new ElfBlacksmith(); private void manufactureWeapons() {
weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); var weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR);
LOGGER.info("{} manufactured {}", blacksmith, weapon); LOGGER.info(weapon.toString());
weapon = blacksmith.manufactureWeapon(WeaponType.AXE); weapon = blacksmith.manufactureWeapon(WeaponType.AXE);
LOGGER.info("{} manufactured {}", blacksmith, weapon); LOGGER.info(weapon.toString());
} }
} }

View File

@ -44,8 +44,4 @@ public class ElfBlacksmith implements Blacksmith {
return ELFARSENAL.get(weaponType); return ELFARSENAL.get(weaponType);
} }
@Override
public String toString() {
return "The elf blacksmith";
}
} }

View File

@ -37,6 +37,6 @@ public class ElfWeapon implements Weapon {
@Override @Override
public String toString() { public String toString() {
return "an elven " + weaponType; return "Elven " + weaponType;
} }
} }

View File

@ -43,9 +43,4 @@ public class OrcBlacksmith implements Blacksmith {
public Weapon manufactureWeapon(WeaponType weaponType) { public Weapon manufactureWeapon(WeaponType weaponType) {
return ORCARSENAL.get(weaponType); return ORCARSENAL.get(weaponType);
} }
@Override
public String toString() {
return "The orc blacksmith";
}
} }

View File

@ -37,6 +37,6 @@ public class OrcWeapon implements Weapon {
@Override @Override
public String toString() { public String toString() {
return "an orcish " + weaponType; return "Orcish " + weaponType;
} }
} }

View File

@ -16,17 +16,16 @@ tags:
## Intent ## Intent
Providing a static method encapsulated in a class called the factory, to hide the implementation Providing a static method encapsulated in a class called factory, in order to hide the
logic and make client code focus on usage rather than initializing new objects. implementation logic and makes client code focus on usage rather then initialization new objects.
## Explanation ## Explanation
Real-world example Real world example
> Imagine an alchemist who is about to manufacture coins. The alchemist must be able to create both > Lets say we have a web application connected to SQLServer, but now we want to switch to Oracle. To
> gold and copper coins and switching between them must be possible without modifying the existing > do so without modifying existing source code, we need to implements Simple Factory pattern, in
> source code. The factory pattern makes it possible by providing a static construction method which > which a static method can be invoked to create connection to a given database.
> can be called with relevant parameters.
Wikipedia says Wikipedia says
@ -35,16 +34,16 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
We have an interface `Coin` and two implementations `GoldCoin` and `CopperCoin`. We have an interface `Car` and two implementations `Ford` and `Ferrari`.
```java ```java
public interface Coin { public interface Car {
String getDescription(); String getDescription();
} }
public class GoldCoin implements Coin { public class Ford implements Car {
static final String DESCRIPTION = "This is a gold coin."; static final String DESCRIPTION = "This is Ford.";
@Override @Override
public String getDescription() { public String getDescription() {
@ -52,9 +51,9 @@ public class GoldCoin implements Coin {
} }
} }
public class CopperCoin implements Coin { public class Ferrari implements Car {
static final String DESCRIPTION = "This is a copper coin."; static final String DESCRIPTION = "This is Ferrari.";
@Override @Override
public String getDescription() { public String getDescription() {
@ -63,48 +62,51 @@ public class CopperCoin implements Coin {
} }
``` ```
Enumeration above represents types of coins that we support (`GoldCoin` and `CopperCoin`). Enumeration above represents types of cars that we support (`Ford` and `Ferrari`).
```java ```java
@RequiredArgsConstructor public enum CarType {
@Getter
public enum CoinType { FORD(Ford::new),
FERRARI(Ferrari::new);
COPPER(CopperCoin::new),
GOLD(GoldCoin::new); private final Supplier<Car> constructor;
private final Supplier<Coin> constructor; CarType(Supplier<Car> constructor) {
this.constructor = constructor;
}
public Supplier<Car> getConstructor() {
return this.constructor;
}
} }
``` ```
Then we have the static method `getCar` to create car objects encapsulated in the factory class
Then we have the static method `getCoin` to create coin objects encapsulated in the factory class `CarsFactory`.
`CoinFactory`.
```java ```java
public class CoinFactory { public class CarsFactory {
public static Coin getCoin(CoinType type) { public static Car getCar(CarType type) {
return type.getConstructor().get(); return type.getConstructor().get();
} }
} }
``` ```
Now on the client code we can create different types of coins using the factory class. Now on the client code we can create different types of cars using the factory class.
```java ```java
LOGGER.info("The alchemist begins his work."); var car1 = CarsFactory.getCar(CarType.FORD);
var coin1 = CoinFactory.getCoin(CoinType.COPPER); var car2 = CarsFactory.getCar(CarType.FERRARI);
var coin2 = CoinFactory.getCoin(CoinType.GOLD); LOGGER.info(car1.getDescription());
LOGGER.info(coin1.getDescription()); LOGGER.info(car2.getDescription());
LOGGER.info(coin2.getDescription());
``` ```
Program output: Program output:
```java ```java
The alchemist begins his work. This is Ford.
This is a copper coin. This is Ferrari.
This is a gold coin.
``` ```
## Class Diagram ## Class Diagram
@ -113,7 +115,7 @@ This is a gold coin.
## Applicability ## Applicability
Use the factory pattern when you only care about the creation of a object, not how to create Use the Simple Factory pattern when you only care about the creation of a object, not how to create
and manage it. and manage it.
Pros Pros
@ -125,13 +127,13 @@ Cons
* The code becomes more complicated than it should be. * The code becomes more complicated than it should be.
## Known uses ## Real world examples
* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) * [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) * [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) * [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) * [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (returns different singleton objects, depending on a protocol) * [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (Returns different singleton objects, depending on a protocol)
* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) * [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E))
* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods. * [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -5,32 +5,31 @@ package com.iluwatar.factory {
+ App() + App()
+ main(args : String[]) {static} + main(args : String[]) {static}
} }
interface Coin { interface Car {
+ getDescription() : String {abstract} + getDescription() : String {abstract}
} }
class CoinFactory { class CarsFactory {
+ CoinFactory() + CarsFactory()
+ getCoin(type : CoinType) : Coin {static} + getCar(type : CarType) : Car {static}
} }
enum CoinType { ~enum CarType {
+ COPPER {static} + FERRARI {static}
+ GOLD {static} + FORD {static}
- constructor : Supplier<Coin> + valueOf(name : String) : CarType {static}
+ getConstructor() : Supplier<Coin> + values() : CarType[] {static}
+ valueOf(name : String) : CoinType {static}
+ values() : CoinType[] {static}
} }
class CopperCoin { class Ferrari {
~ DESCRIPTION : String {static} ~ DESCRIPTION : String {static}
+ CopperCoin() + Ferrari()
+ getDescription() : String + getDescription() : String
} }
class GoldCoin { class Ford {
~ DESCRIPTION : String {static} ~ DESCRIPTION : String {static}
+ GoldCoin() + Ford()
+ getDescription() : String + getDescription() : String
} }
} }
CopperCoin ..|> Coin CarType ..+ CarsFactory
GoldCoin ..|> Coin Ferrari ..|> Car
Ford ..|> Car
@enduml @enduml

View File

@ -30,8 +30,8 @@ import lombok.extern.slf4j.Slf4j;
* create and return objects of varying classes, in order to hide the implementation logic * create and return objects of varying classes, in order to hide the implementation logic
* and makes client code focus on usage rather then objects initialization and management. * and makes client code focus on usage rather then objects initialization and management.
* *
* <p>In this example an alchemist manufactures coins. CoinFactory is the factory class and it * <p>In this example the CarFactory is the factory class and it provides a static method to
* provides a static method to create different types of coins. * create different cars.
*/ */
@Slf4j @Slf4j
@ -41,10 +41,9 @@ public class App {
* Program main entry point. * Program main entry point.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
LOGGER.info("The alchemist begins his work."); var car1 = CarsFactory.getCar(CarType.FORD);
var coin1 = CoinFactory.getCoin(CoinType.COPPER); var car2 = CarsFactory.getCar(CarType.FERRARI);
var coin2 = CoinFactory.getCoin(CoinType.GOLD); LOGGER.info(car1.getDescription());
LOGGER.info(coin1.getDescription()); LOGGER.info(car2.getDescription());
LOGGER.info(coin2.getDescription());
} }
} }

View File

@ -24,9 +24,9 @@
package com.iluwatar.factory; package com.iluwatar.factory;
/** /**
* Coin interface. * Car interface.
*/ */
public interface Coin { public interface Car {
String getDescription(); String getDescription();

View File

@ -28,14 +28,15 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
/** /**
* Enumeration for different types of coins. * Enumeration for different types of cars.
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public enum CoinType { public enum CarType {
COPPER(CopperCoin::new), FORD(Ford::new),
GOLD(GoldCoin::new); FERRARI(Ferrari::new);
private final Supplier<Car> constructor;
private final Supplier<Coin> constructor;
} }

View File

@ -24,14 +24,14 @@
package com.iluwatar.factory; package com.iluwatar.factory;
/** /**
* Factory of coins. * Factory of cars.
*/ */
public class CoinFactory { public class CarsFactory {
/** /**
* Factory method takes as a parameter the coin type and calls the appropriate class. * Factory method takes as parameter a car type and initiate the appropriate class.
*/ */
public static Coin getCoin(CoinType type) { public static Car getCar(CarType type) {
return type.getConstructor().get(); return type.getConstructor().get();
} }
} }

View File

@ -24,11 +24,11 @@
package com.iluwatar.factory; package com.iluwatar.factory;
/** /**
* GoldCoin implementation. * Ferrari implementation.
*/ */
public class GoldCoin implements Coin { public class Ferrari implements Car {
static final String DESCRIPTION = "This is a gold coin."; static final String DESCRIPTION = "This is Ferrari.";
@Override @Override
public String getDescription() { public String getDescription() {

View File

@ -24,11 +24,11 @@
package com.iluwatar.factory; package com.iluwatar.factory;
/** /**
* CopperCoin implementation. * Ford implementation.
*/ */
public class CopperCoin implements Coin { public class Ford implements Car {
static final String DESCRIPTION = "This is a copper coin."; static final String DESCRIPTION = "This is Ford.";
@Override @Override
public String getDescription() { public String getDescription() {

View File

@ -27,11 +27,12 @@ import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class CoinFactoryTest { class CarsFactoryTest {
@Test @Test
void shouldReturnGoldCoinInstance() { void shouldReturnFerrariInstance() {
final var goldCoin = CoinFactory.getCoin(CoinType.GOLD); final var ferrari = CarsFactory.getCar(CarType.FERRARI);
assertTrue(goldCoin instanceof GoldCoin); assertTrue(ferrari instanceof Ferrari);
} }
} }

View File

@ -1,120 +0,0 @@
---
layout: pattern
title: Fan-Out/Fan-In
folder: fanout-fanin
permalink: /patterns/fanout-fanin/
categories: 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.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,39 +0,0 @@
@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

View File

@ -1,69 +0,0 @@
<?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

@ -1,74 +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.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);
}
}

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