Merge branch 'master' into all-contributors/add-ravening

This commit is contained in:
Ilkka Seppälä 2020-09-06 19:38:44 +03:00 committed by GitHub
commit 9d75592e8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
533 changed files with 7805 additions and 2802 deletions

View File

@ -1085,6 +1085,116 @@
"code", "code",
"review" "review"
] ]
},
{
"login": "vINCENT8888801",
"name": "Wei Seng",
"avatar_url": "https://avatars0.githubusercontent.com/u/8037883?v=4",
"profile": "https://github.com/vINCENT8888801",
"contributions": [
"code"
]
},
{
"login": "ashishtrivedi16",
"name": "Ashish Trivedi",
"avatar_url": "https://avatars3.githubusercontent.com/u/23194128?v=4",
"profile": "https://www.linkedin.com/in/ashish-trivedi-218379135/",
"contributions": [
"code"
]
},
{
"login": "RayYH",
"name": "洪月阳",
"avatar_url": "https://avatars1.githubusercontent.com/u/41055099?v=4",
"profile": "https://rayyounghong.com",
"contributions": [
"code"
]
},
{
"login": "xdvrx1",
"name": "xdvrx1",
"avatar_url": "https://avatars0.githubusercontent.com/u/47092464?v=4",
"profile": "https://xdvrx1.github.io/",
"contributions": [
"review",
"ideas"
]
},
{
"login": "ohbus",
"name": "Subhrodip Mohanta",
"avatar_url": "https://avatars0.githubusercontent.com/u/13291222?v=4",
"profile": "http://subho.xyz",
"contributions": [
"code",
"review"
]
},
{
"login": "nahteb",
"name": "Bethan Palmer",
"avatar_url": "https://avatars3.githubusercontent.com/u/13121570?v=4",
"profile": "https://github.com/nahteb",
"contributions": [
"code"
]
},
{
"login": "ToxicDreamz",
"name": "Toxic Dreamz",
"avatar_url": "https://avatars0.githubusercontent.com/u/45225562?v=4",
"profile": "https://github.com/ToxicDreamz",
"contributions": [
"code"
]
},
{
"login": "edycutjong",
"name": "Edy Cu Tjong",
"avatar_url": "https://avatars1.githubusercontent.com/u/1098102?v=4",
"profile": "http://www.edycutjong.com",
"contributions": [
"doc"
]
},
{
"login": "mkrzywanski",
"name": "Michał Krzywański",
"avatar_url": "https://avatars0.githubusercontent.com/u/15279585?v=4",
"profile": "https://github.com/mkrzywanski",
"contributions": [
"code"
]
},
{
"login": "stefanbirkner",
"name": "Stefan Birkner",
"avatar_url": "https://avatars1.githubusercontent.com/u/711349?v=4",
"profile": "https://www.stefan-birkner.de",
"contributions": [
"code"
]
},
{
"login": "fedorskvorcov",
"name": "Fedor Skvorcov",
"avatar_url": "https://avatars3.githubusercontent.com/u/43882212?v=4",
"profile": "https://github.com/fedorskvorcov",
"contributions": [
"code"
]
},
{
"login": "samilAyoub",
"name": "samilAyoub",
"avatar_url": "https://avatars0.githubusercontent.com/u/61546990?v=4",
"profile": "https://github.com/samilAyoub",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 4, "contributorsPerLine": 4,

View File

@ -24,37 +24,41 @@
# This workflow will build a Java project with Maven # This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven name: Java CI
on: on:
push: push:
branches: [ master ] branches: [ master ]
pull_request:
branches: [ master ]
jobs: jobs:
build: build:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
# Disabling shallow clone for improving relevancy of SonarQube reporting
fetch-depth: 0
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 11
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Some tests need screen access # Some tests need screen access
- name: Install xvfb - name: Install xvfb
run: sudo apt-get install xvfb run: sudo apt-get install -y xvfb
# SonarQube scan does not work for forked repositories # The SonarQube analysis is only for the master branch of the main repository.
# SonarQube scan does not work for forked repositories try changing it to xvfb-run mvn clean verify
# See https://jira.sonarsource.com/browse/MMF-1371 # See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
if: github.ref != 'refs/heads/master'
run: xvfb-run mvn clean verify
- name: Build with Maven and run SonarQube analysis - name: Build with Maven and run SonarQube analysis
if: github.ref == 'refs/heads/master'
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
env: env:
# These two env variables are needed for sonar analysis # These two env variables are needed for sonar analysis
GITHUB_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

57
.github/workflows/maven-pr-builder.yml vendored Normal file
View File

@ -0,0 +1,57 @@
#
# The MIT License
# Copyright © 2014-2019 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.
#
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java PR Builder
on:
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Some tests need screen access
- name: Install xvfb
run: sudo apt-get install -y xvfb
# This worflow is only for building Pull Requests, the master branch runs Sonar analysis on the main repository.
# SonarQube scan does not work for forked repositories.
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
run: xvfb-run mvn clean verify

View File

@ -6,10 +6,11 @@
![Java CI with Maven](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI%20with%20Maven/badge.svg) ![Java CI with Maven](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI%20with%20Maven/badge.svg)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](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)
[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=alert_status)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
<!-- 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-119-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-131-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
# Introduction # Introduction
@ -245,7 +246,26 @@ This project is licensed under the terms of the MIT license.
<tr> <tr>
<td align="center"><a href="https://github.com/nishant"><img src="https://avatars2.githubusercontent.com/u/15331971?v=4" width="100px;" alt=""/><br /><sub><b>Nishant Arora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nishant" title="Code">💻</a></td> <td align="center"><a href="https://github.com/nishant"><img src="https://avatars2.githubusercontent.com/u/15331971?v=4" width="100px;" alt=""/><br /><sub><b>Nishant Arora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nishant" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/raja-peeyush-kumar-singh"><img src="https://avatars0.githubusercontent.com/u/5496024?v=4" width="100px;" alt=""/><br /><sub><b>Peeyush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=raja-peeyush-kumar-singh" title="Code">💻</a></td> <td align="center"><a href="https://github.com/raja-peeyush-kumar-singh"><img src="https://avatars0.githubusercontent.com/u/5496024?v=4" width="100px;" alt=""/><br /><sub><b>Peeyush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=raja-peeyush-kumar-singh" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/vINCENT8888801"><img src="https://avatars0.githubusercontent.com/u/8037883?v=4" width="100px;" alt=""/><br /><sub><b>Wei Seng</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vINCENT8888801" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.linkedin.com/in/ashish-trivedi-218379135/"><img src="https://avatars3.githubusercontent.com/u/23194128?v=4" width="100px;" alt=""/><br /><sub><b>Ashish Trivedi</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ashishtrivedi16" title="Code">💻</a></td>
<td align="center"><a href="https://rayyounghong.com"><img src="https://avatars1.githubusercontent.com/u/41055099?v=4" width="100px;" alt=""/><br /><sub><b>洪月阳</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=RayYH" title="Code">💻</a></td>
<td align="center"><a href="https://xdvrx1.github.io/"><img src="https://avatars0.githubusercontent.com/u/47092464?v=4" width="100px;" alt=""/><br /><sub><b>xdvrx1</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Axdvrx1" title="Reviewed Pull Requests">👀</a> <a href="#ideas-xdvrx1" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://subho.xyz"><img src="https://avatars0.githubusercontent.com/u/13291222?v=4" width="100px;" alt=""/><br /><sub><b>Subhrodip Mohanta</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ohbus" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aohbus" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/nahteb"><img src="https://avatars3.githubusercontent.com/u/13121570?v=4" width="100px;" alt=""/><br /><sub><b>Bethan Palmer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nahteb" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ToxicDreamz"><img src="https://avatars0.githubusercontent.com/u/45225562?v=4" width="100px;" alt=""/><br /><sub><b>Toxic Dreamz</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ToxicDreamz" title="Code">💻</a></td>
<td align="center"><a href="http://www.edycutjong.com"><img src="https://avatars1.githubusercontent.com/u/1098102?v=4" width="100px;" alt=""/><br /><sub><b>Edy Cu Tjong</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=edycutjong" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/mkrzywanski"><img src="https://avatars0.githubusercontent.com/u/15279585?v=4" width="100px;" alt=""/><br /><sub><b>Michał Krzywański</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=mkrzywanski" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aravening" title="Reviewed Pull Requests">👀</a></td> <td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aravening" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://www.stefan-birkner.de"><img src="https://avatars1.githubusercontent.com/u/711349?v=4" width="100px;" alt=""/><br /><sub><b>Stefan Birkner</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=stefanbirkner" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/fedorskvorcov"><img src="https://avatars3.githubusercontent.com/u/43882212?v=4" width="100px;" alt=""/><br /><sub><b>Fedor Skvorcov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=fedorskvorcov" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/samilAyoub"><img src="https://avatars0.githubusercontent.com/u/61546990?v=4" width="100px;" alt=""/><br /><sub><b>samilAyoub</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=samilAyoub" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>abstract-document</artifactId> <artifactId>abstract-document</artifactId>
<dependencies> <dependencies>

View File

@ -25,14 +25,23 @@ package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Simple App test * Simple App test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void shouldExecuteAppWithoutException() { void shouldExecuteAppWithoutException() {
App.main(null); assertDoesNotThrow(() -> App.main(null));
} }
} }

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>abstract-factory</artifactId> <artifactId>abstract-factory</artifactId>
<dependencies> <dependencies>

View File

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

View File

@ -101,7 +101,7 @@ public class ConfigureForUnixVisitor implements ZoomVisitor {
} }
``` ```
Finally here are the visitors in action. Finally, here are the visitors in action.
```java ```java
var conUnix = new ConfigureForUnixVisitor(); var conUnix = new ConfigureForUnixVisitor();

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>acyclic-visitor</artifactId> <artifactId>acyclic-visitor</artifactId>

View File

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

View File

@ -12,9 +12,8 @@ tags:
Wrapper Wrapper
## Intent ## Intent
Convert the interface of a class into another interface the clients Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that
expect. Adapter lets classes work together that couldn't otherwise because of couldn't otherwise because of incompatible interfaces.
incompatible interfaces.
## Explanation ## Explanation

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>adapter</artifactId> <artifactId>adapter</artifactId>
<dependencies> <dependencies>

View File

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

View File

@ -6,6 +6,8 @@ permalink: /patterns/aggregator-microservices/
categories: Architectural categories: Architectural
tags: tags:
- Cloud distributed - Cloud distributed
- Decoupling
- Microservices
--- ---
## Intent ## Intent
@ -17,7 +19,7 @@ The user makes a single call to the aggregator service, and the aggregator then
Real world example Real world example
> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator > Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator
> service that in turn calls the product information microservice and product inventory microservice returning the > service which in turn calls the product information microservice and product inventory microservice returning the
> combined information. > combined information.
In plain words In plain words
@ -41,7 +43,7 @@ public class Product {
} }
``` ```
Next we can introduct our `Aggregator` microservice. It contains clients `ProductInformationClient` and Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and
`ProductInventoryClient` for calling respective microservices. `ProductInventoryClient` for calling respective microservices.
```java ```java

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-service</artifactId> <artifactId>aggregator-service</artifactId>

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>inventory-microservice</artifactId> <artifactId>inventory-microservice</artifactId>

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>

View File

@ -10,28 +10,37 @@ tags:
--- ---
## Intent ## Intent
Provide a helper service instance on a client and offload common functionality away from a shared resource. Provide a helper service instance on a client and offload common functionality away from a shared resource.
## Explanation ## Explanation
Real world example Real world example
> A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging. > A remote service has many clients accessing a function it provides. The service is a legacy application and is
> impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request
> frequency should be implemented along with latency checks and client-side logging.
In plain words In plain words
> Using the ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging. > With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and
> logging.
Microsoft documentation states Microsoft documentation states
> An ambassador service can be thought of as an out-of-process proxy that is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features. > An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern
> can be useful for offloading common client connectivity tasks such as monitoring, logging, routing,
> security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications,
> or other applications that are difficult to modify, in order to extend their networking capabilities. It can also
> enable a specialized team to implement those features.
**Programmatic Example** **Programmatic Example**
With the above example in mind we will imitate the functionality in a simple manner. We have an interface implemented by the remote service as well as the ambassador service: With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented
by the remote service as well as the ambassador service:
```java ```java
interface RemoteServiceInterface { interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception; long doRemoteFunction(int value) throws Exception;
} }
``` ```
@ -136,7 +145,7 @@ public class Client {
} }
``` ```
And here are two clients using the service. Here are two clients using the service.
```java ```java
public class App { public class App {
@ -149,13 +158,29 @@ public class App {
} }
``` ```
Here's the output for running the example:
```java
Time taken (ms): 111
Service result: 120
Time taken (ms): 931
Failed to reach remote: (1)
Time taken (ms): 665
Failed to reach remote: (2)
Time taken (ms): 538
Failed to reach remote: (3)
Service result: -1
```
## Class diagram ## Class diagram
![alt text](./etc/ambassador.urm.png "Ambassador class diagram") ![alt text](./etc/ambassador.urm.png "Ambassador class diagram")
## Applicability ## Applicability
Ambassador is applicable when working with a legacy remote service that cannot
be modified or would be extremely difficult to modify. Connectivity features can Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely
be implemented on the client avoiding the need for changes on the remote service. difficult to modify. Connectivity features can be implemented on the client avoiding the need for changes on the remote
service.
* Ambassador provides a local interface for a remote service. * Ambassador provides a local interface for a remote service.
* Ambassador provides logging, circuit breaking, retries and security on the client. * Ambassador provides logging, circuit breaking, retries and security on the client.
@ -168,10 +193,14 @@ be implemented on the client avoiding the need for changes on the remote service
* Offload remote service tasks * Offload remote service tasks
* Facilitate network connection * Facilitate network connection
## Real world examples ## Known uses
* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador) * [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
## Related patterns
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
## Credits ## Credits
* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) * [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ambassador</artifactId> <artifactId>ambassador</artifactId>

View File

@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface {
* *
* @param value integer value to be multiplied. * @param value integer value to be multiplied.
* @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10, * @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10,
* otherwise {@link RemoteServiceInterface#FAILURE}. * otherwise {@link RemoteServiceStatus#FAILURE}.
*/ */
@Override @Override
public long doRemoteFunction(int value) { public long doRemoteFunction(int value) {
@ -74,6 +74,7 @@ public class RemoteService implements RemoteServiceInterface {
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e); LOGGER.error("Thread sleep state interrupted", e);
} }
return waitTime <= THRESHOLD ? value * 10 : FAILURE; return waitTime <= THRESHOLD ? value * 10
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
} }
} }

View File

@ -27,7 +27,6 @@ package com.iluwatar.ambassador;
* Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). * Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}).
*/ */
interface RemoteServiceInterface { interface RemoteServiceInterface {
int FAILURE = -1;
long doRemoteFunction(int value) throws Exception; long doRemoteFunction(int value);
} }

View File

@ -0,0 +1,48 @@
/*
* The MIT License
* Copyright © 2014-2019 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.ambassador;
/**
* Holds information regarding the status of the Remote Service.
*
* <p> This Enum replaces the integer value previously
* stored in {@link RemoteServiceInterface} as SonarCloud was identifying
* it as an issue. All test cases have been checked after changes,
* without failures. </p>
*/
public enum RemoteServiceStatus {
FAILURE(-1)
;
private final long remoteServiceStatusValue;
RemoteServiceStatus(long remoteServiceStatusValue) {
this.remoteServiceStatusValue = remoteServiceStatusValue;
}
public long getRemoteServiceStatusValue() {
return remoteServiceStatusValue;
}
}

View File

@ -23,6 +23,7 @@
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
import static java.lang.Thread.sleep; import static java.lang.Thread.sleep;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,14 +59,14 @@ public class ServiceAmbassador implements RemoteServiceInterface {
private long safeCall(int value) { private long safeCall(int value) {
var retries = 0; var retries = 0;
var result = (long) FAILURE; var result = FAILURE.getRemoteServiceStatusValue();
for (int i = 0; i < RETRIES; i++) { for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) { if (retries >= RETRIES) {
return FAILURE; return FAILURE.getRemoteServiceStatusValue();
} }
if ((result = checkLatency(value)) == FAILURE) { if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")"); LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++; retries++;
try { try {

View File

@ -25,13 +25,23 @@ package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -37,6 +37,6 @@ class ClientTest {
Client client = new Client(); Client client = new Client();
var result = client.useService(10); var result = client.useService(10);
assertTrue(result == 100 || result == RemoteService.FAILURE); assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }

View File

@ -37,7 +37,7 @@ class RemoteServiceTest {
void testFailedCall() { void testFailedCall() {
var remoteService = new RemoteService(new StaticRandomProvider(0.21)); var remoteService = new RemoteService(new StaticRandomProvider(0.21));
var result = remoteService.doRemoteFunction(10); var result = remoteService.doRemoteFunction(10);
assertEquals(RemoteServiceInterface.FAILURE, result); assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result);
} }
@Test @Test

View File

@ -35,6 +35,6 @@ class ServiceAmbassadorTest {
@Test @Test
void test() { void test() {
long result = new ServiceAmbassador().doRemoteFunction(10); long result = new ServiceAmbassador().doRemoteFunction(10);
assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE); assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }

View File

@ -12,49 +12,53 @@ tags:
## Intent ## Intent
Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway, Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call
and the API Gateway then calls each relevant microservice. to the API Gateway, and the API Gateway then calls each relevant microservice.
## Explanation ## Explanation
With the Microservices pattern, a client may need data from multiple different microservices. If the client called each With the Microservices pattern, a client may need data from multiple different microservices. If the
microservice directly, that could contribute to longer load times, since the client would have to make a network request client called each microservice directly, that could contribute to longer load times, since the
for each microservice called. Moreover, having the client call each microservice directly ties the client to that client would have to make a network request for each microservice called. Moreover, having the
microservice - if the internal implementations of the microservices change (for example, if two microservices are client call each microservice directly ties the client to that microservice - if the internal
combined sometime in the future) or if the location (host and port) of a microservice changes, then every client that implementations of the microservices change (for example, if two microservices are combined sometime
in the future) or if the location (host and port) of a microservice changes, then every client that
makes use of those microservices must be updated. makes use of those microservices must be updated.
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway
entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate pattern, an additional entity (the API Gateway) is placed between the client and the microservices.
the calls to the microservices. Rather than the client calling each microservice individually, the client calls the The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client
API Gateway a single time. The API Gateway then calls each of the microservices that the client needs. calling each microservice individually, the client calls the API Gateway a single time. The API
Gateway then calls each of the microservices that the client needs.
Real world example Real world example
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes > We are implementing microservices and API Gateway pattern for an e-commerce site. In this system
calls to the Image and Price microservices. > the API Gateway makes calls to the Image and Price microservices.
In plain words In plain words
> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the > For a system implemented using microservices architecture, API Gateway is the single entry point
calls to the individual microservices. > that aggregates the calls to the individual microservices.
Wikipedia says Wikipedia says
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security > API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling
policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often > and security policies, passes requests to the back-end service and then passes the response back
includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also > to the requester. A gateway often includes a transformation engine to orchestrate and modify the
provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to > requests and responses on the fly. A gateway can also provide functionality such as collecting
support authentication, authorization, security, audit and regulatory compliance. > analytics data and providing caching. The gateway can provide functionality to support
> authentication, authorization, security, audit and regulatory compliance.
**Programmatic Example** **Programmatic Example**
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes This implementation shows what the API Gateway pattern could look like for an e-commerce site. The
calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers `ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and
viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls `PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price
both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price information and an image of a product, so the `ApiGateway` calls both of the microservices and
information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which aggregates the data in the `DesktopProduct` model. However, mobile users only see price information;
it uses to populate the `MobileProduct`. they do not see a product image. For mobile users, the `ApiGateway` only retrieves price
information, which it uses to populate the `MobileProduct`.
Here's the Image microservice implementation. Here's the Image microservice implementation.
@ -64,7 +68,6 @@ public interface ImageClient {
} }
public class ImageClientImpl implements ImageClient { public class ImageClientImpl implements ImageClient {
@Override @Override
public String getImagePath() { public String getImagePath() {
var httpClient = HttpClient.newHttpClient(); var httpClient = HttpClient.newHttpClient();
@ -114,7 +117,7 @@ public class PriceClientImpl implements PriceClient {
} }
``` ```
And here we can see how API Gateway maps the requests to the microservices. Here we can see how API Gateway maps the requests to the microservices.
```java ```java
public class ApiGateway { public class ApiGateway {

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway-service</artifactId> <artifactId>api-gateway-service</artifactId>

View File

@ -23,11 +23,16 @@
package com.iluwatar.api.gateway; package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -35,6 +40,8 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class ImageClientImpl implements ImageClient { public class ImageClientImpl implements ImageClient {
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
/** /**
* Makes a simple HTTP Get request to the Image microservice. * Makes a simple HTTP Get request to the Image microservice.
* *
@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient {
.build(); .build();
try { try {
LOGGER.info("Sending request to fetch image path");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body(); return httpResponse.body();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(); LOGGER.error("Failure occurred while getting image path", e);
} }
return null; return null;
} }
private void logResponse(HttpResponse<String> httpResponse) {
if (isSuccessResponse(httpResponse.statusCode())) {
LOGGER.info("Image path received successfully");
} else {
LOGGER.warn("Image path request failed");
}
}
private boolean isSuccessResponse(int responseCode) {
return responseCode >= 200 && responseCode <= 299;
}
} }

View File

@ -23,18 +23,26 @@
package com.iluwatar.api.gateway; package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* An adapter to communicate with the Price microservice. * An adapter to communicate with the Price microservice.
*/ */
@Component @Component
public class PriceClientImpl implements PriceClient { public class PriceClientImpl implements PriceClient {
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
/** /**
* Makes a simple HTTP Get request to the Price microservice. * Makes a simple HTTP Get request to the Price microservice.
* *
@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient {
.build(); .build();
try { try {
LOGGER.info("Sending request to fetch price info");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body(); return httpResponse.body();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(); LOGGER.error("Failure occurred while getting price info", e);
} }
return null; return null;
} }
private void logResponse(HttpResponse<String> httpResponse) {
if (isSuccessResponse(httpResponse.statusCode())) {
LOGGER.info("Price info received successfully");
} else {
LOGGER.warn("Price info request failed");
}
}
private boolean isSuccessResponse(int responseCode) {
return responseCode >= 200 && responseCode <= 299;
}
} }

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>image-microservice</artifactId> <artifactId>image-microservice</artifactId>

View File

@ -23,15 +23,20 @@
package com.iluwatar.image.microservice; package com.iluwatar.image.microservice;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* Exposes the Image microservice's endpoints. * Exposes the Image microservice's endpoints.
*/ */
@RestController @RestController
public class ImageController { public class ImageController {
private static final Logger LOGGER = getLogger(ImageController.class);
/** /**
* An endpoint for a user to retrieve an image path. * An endpoint for a user to retrieve an image path.
@ -40,6 +45,7 @@ public class ImageController {
*/ */
@RequestMapping(value = "/image-path", method = RequestMethod.GET) @RequestMapping(value = "/image-path", method = RequestMethod.GET)
public String getImagePath() { public String getImagePath() {
LOGGER.info("Successfully found image path");
return "/product-image.png"; return "/product-image.png";
} }
} }

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -23,15 +23,20 @@
package com.iluwatar.price.microservice; package com.iluwatar.price.microservice;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* Exposes the Price microservice's endpoints. * Exposes the Price microservice's endpoints.
*/ */
@RestController @RestController
public class PriceController { public class PriceController {
private static final Logger LOGGER = getLogger(PriceController.class);
/** /**
* An endpoint for a user to retrieve a product's price. * An endpoint for a user to retrieve a product's price.
@ -40,6 +45,7 @@ public class PriceController {
*/ */
@RequestMapping(value = "/price", method = RequestMethod.GET) @RequestMapping(value = "/price", method = RequestMethod.GET)
public String getPrice() { public String getPrice() {
LOGGER.info("Successfully found price info");
return "20"; return "20";
} }
} }

View File

@ -9,22 +9,137 @@ tags:
--- ---
## Also known as ## Also known as
Given/When/Then Given/When/Then
## Intent ## Intent
The Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
It breaks tests down into three clear and distinct steps: It breaks tests down into three clear and distinct steps:
1. Arrange: Perform the setup and initialization required for the test. 1. Arrange: Perform the setup and initialization required for the test.
2. Act: Take action(s) required for the test. 2. Act: Take action(s) required for the test.
3. Assert: Verify the outcome(s) of the test. 3. Assert: Verify the outcome(s) of the test.
## Explanation
This pattern has several significant benefits. It creates a clear separation between a test's
setup, operations, and results. This structure makes the code easier to read and understand. If
you place the steps in order and format your code to separate them, you can scan a test and
quickly comprehend what it does.
It also enforces a certain degree of discipline when you write your tests. You have to think
clearly about the three steps your test will perform. It makes tests more natural to write at
the same time since you already have an outline.
Real world example
> We need to write comprehensive and clear unit test suite for a class.
In plain words
> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy
> maintenance.
WikiWikiWeb says
> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods.
**Programmatic Example**
Let's first introduce our `Cash` class to be unit tested.
```java
public class Cash {
private int amount;
Cash(int amount) {
this.amount = amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
}
}
int count() {
return amount;
}
}
```
Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly
separated steps for each unit test.
```java
public class CashAAATest {
@Test
public void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
cash.plus(4);
//Assert
assertEquals(7, cash.count());
}
@Test
public void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
var result = cash.minus(5);
//Assert
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
public void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
var result = cash.minus(6);
//Assert
assertFalse(result);
assertEquals(1, cash.count());
}
@Test
public void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act
cash.plus(6);
var result = cash.minus(3);
//Assert
assertTrue(result);
assertEquals(8, cash.count());
}
}
```
## Applicability ## Applicability
Use Arrange/Act/Assert pattern when Use Arrange/Act/Assert pattern when
* you need to structure your unit tests so they're easier to read, maintain, and enhance. * You need to structure your unit tests so that they're easier to read, maintain, and enhance.
## Credits ## Credits
* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) * [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx)
* [Bill Wake: 3A Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) * [Bill Wake: 3A Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/)
* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html) * [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html)
* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf)
* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35)
* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05)

View File

@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>async-method-invocation</artifactId> <artifactId>async-method-invocation</artifactId>
<dependencies> <dependencies>

View File

@ -25,12 +25,24 @@ package com.iluwatar.async.method.invocation;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() throws Exception { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -24,15 +24,26 @@
package com.iluwatar.balking; package com.iluwatar.balking;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void main() { void shouldExecuteApplicationWithoutException() {
App.main(); assertDoesNotThrow((Executable) App::main);
} }
} }

View File

@ -9,20 +9,26 @@ tags:
--- ---
## Also known as ## Also known as
Handle/Body Handle/Body
## Intent ## Intent
Decouple an abstraction from its implementation so that the two can vary independently. Decouple an abstraction from its implementation so that the two can vary independently.
## Explanation ## Explanation
Real world example Real world example
> 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 of the weapons for each of the enchantments or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the second. > 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
> of the weapons for each of the enchantments or would you just create separate enchantment and set
> it for the weapon as needed? Bridge pattern allows you to do the second.
In Plain Words In Plain Words
> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy. > Bridge pattern is about preferring composition over inheritance. Implementation details are pushed
> from a hierarchy to another object with a separate hierarchy.
Wikipedia says Wikipedia says
@ -30,7 +36,7 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
Translating our weapon example from above. Here we have the `Weapon` hierarchy Translating our weapon example from above. Here we have the `Weapon` hierarchy:
```java ```java
public interface Weapon { public interface Weapon {
@ -105,7 +111,7 @@ public class Hammer implements Weapon {
} }
``` ```
And the separate enchantment hierarchy Here's the separate enchantment hierarchy:
```java ```java
public interface Enchantment { public interface Enchantment {
@ -151,7 +157,7 @@ public class SoulEatingEnchantment implements Enchantment {
} }
``` ```
And both the hierarchies in action Here are both hierarchies in action:
```java ```java
var enchantedSword = new Sword(new SoulEatingEnchantment()); var enchantedSword = new Sword(new SoulEatingEnchantment());
@ -178,18 +184,21 @@ hammer.unwield();
``` ```
## Class diagram ## Class diagram
![alt text](./etc/bridge.urm.png "Bridge class diagram") ![alt text](./etc/bridge.urm.png "Bridge class diagram")
## Applicability ## Applicability
Use the Bridge pattern when Use the Bridge pattern when
* you want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time. * You want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time.
* both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently * Both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently.
* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled. * Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies * You have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies.
* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation. * You want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation.
## Tutorial ## Tutorial
* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) * [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java)
## Credits ## Credits

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>bridge</artifactId> <artifactId>bridge</artifactId>
<dependencies> <dependencies>

View File

@ -25,12 +25,22 @@ package com.iluwatar.bridge;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -9,36 +9,47 @@ tags:
--- ---
## Intent ## Intent
Separate the construction of a complex object from its
representation so that the same construction process can create different Separate the construction of a complex object from its representation so that the same construction
representations. 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 computer create the character for you. But if you want to select the character details like profession, gender, hair color etc. the character generation becomes a step-by-step process that completes when all the selections are ready. > 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
> profession, gender, hair color etc. the character generation becomes a step-by-step process that
> completes when all the selections are ready.
In plain words In plain words
> Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object. > Allows you to create different flavors of an object while avoiding constructor pollution. Useful
> when there could be several flavors of an object. Or when there are a lot of steps involved in
> creation of an object.
Wikipedia says Wikipedia says
> The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern. > The builder pattern is an object creation software design pattern with the intentions of finding
> a solution to the telescoping constructor anti-pattern.
Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point or the other we have all seen a constructor like below: Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point
or the other, we have all seen a constructor like below:
```java ```java
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
} }
``` ```
As you can see the number of constructor parameters can quickly get out of hand and it might become difficult to understand the arrangement of parameters. Plus this parameter list could keep on growing if you would want to add more options in future. This is called telescoping constructor anti-pattern. As you can see the number of constructor parameters can quickly get out of hand, and it may become
difficult to understand the arrangement of parameters. Plus this parameter list could keep on
growing if you would want to add more options in the future. This is called telescoping constructor
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 create The sane alternative is to use the Builder pattern. First of all we have our hero that we want to
create:
```java ```java
public final class Hero { public final class Hero {
@ -60,7 +71,7 @@ public final class Hero {
} }
``` ```
And then we have the builder Then we have the builder:
```java ```java
public static class Builder { public static class Builder {
@ -105,20 +116,22 @@ And then we have the builder
} }
``` ```
And then it can be used as: Then it can be used as:
```java ```java
var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();
``` ```
## Class diagram ## Class diagram
![alt text](./etc/builder.urm.png "Builder class diagram") ![alt text](./etc/builder.urm.png "Builder class diagram")
## Applicability ## Applicability
Use the Builder pattern when 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
## Real world examples ## Real world examples

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>builder</artifactId> <artifactId>builder</artifactId>
<dependencies> <dependencies>

View File

@ -25,12 +25,23 @@ package com.iluwatar.builder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>business-delegate</artifactId> <artifactId>business-delegate</artifactId>
<dependencies> <dependencies>

View File

@ -27,13 +27,23 @@ import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Business Delegate example runs without errors. * Tests that Business Delegate example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() throws IOException { void shouldExecuteApplicationWithoutException() {
String[] args = {};
App.main(args); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -58,12 +58,14 @@ public class App {
var vm = new VirtualMachine(); var vm = new VirtualMachine();
vm.getWizards()[0] = wizard; vm.getWizards()[0] = wizard;
interpretInstruction("LITERAL 0", vm); String literal = "LITERAL 0";
interpretInstruction("LITERAL 0", vm);
interpretInstruction(literal, vm);
interpretInstruction(literal, vm);
interpretInstruction("GET_HEALTH", vm); interpretInstruction("GET_HEALTH", vm);
interpretInstruction("LITERAL 0", vm); interpretInstruction(literal, vm);
interpretInstruction("GET_AGILITY", vm); interpretInstruction("GET_AGILITY", vm);
interpretInstruction("LITERAL 0", vm); interpretInstruction(literal, vm);
interpretInstruction("GET_WISDOM ", vm); interpretInstruction("GET_WISDOM ", vm);
interpretInstruction("ADD", vm); interpretInstruction("ADD", vm);
interpretInstruction("LITERAL 2", vm); interpretInstruction("LITERAL 2", vm);

View File

@ -25,13 +25,22 @@ package com.iluwatar.bytecode;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>caching</artifactId> <artifactId>caching</artifactId>
<dependencies> <dependencies>

View File

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

View File

@ -9,14 +9,16 @@ tags:
--- ---
## Intent ## Intent
Callback is a piece of executable code that is passed as an argument to other code, which is expected to call back
(execute) the argument at some convenient time. Callback is a piece of executable code that is passed as an argument to other code, which is
expected to call back (execute) the argument at some convenient time.
## Explanation ## Explanation
Real world example Real world example
> We need to be notified after executing task has finished. We pass a callback method for the executor and wait for it to call back on us. > We need to be notified after executing task has finished. We pass a callback method for
> the executor and wait for it to call back on us.
In plain words In plain words
@ -24,7 +26,9 @@ In plain words
Wikipedia says Wikipedia says
> In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time. > In computer programming, a callback, also known as a "call-after" function, is any executable
> code that is passed as an argument to other code; that other code is expected to call
> back (execute) the argument at a given time.
**Programmatic Example** **Programmatic Example**
@ -61,7 +65,7 @@ public final class SimpleTask extends Task {
} }
``` ```
Finally here's how we execute a task and receive a callback when it's finished. Finally, here's how we execute a task and receive a callback when it's finished.
```java ```java
var task = new SimpleTask(); var task = new SimpleTask();
@ -69,13 +73,15 @@ Finally here's how we execute a task and receive a callback when it's finished.
``` ```
## Class diagram ## Class diagram
![alt text](./etc/callback.png "Callback") ![alt text](./etc/callback.png "Callback")
## Applicability ## Applicability
Use the Callback pattern when Use the Callback pattern when
* when some arbitrary synchronous or asynchronous action must be performed after execution of some defined activity. * when some arbitrary synchronous or asynchronous action must be performed after execution of some defined activity.
## Real world examples ## Real world examples
* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept callback that will be triggered every time when barrier is tripped. * [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept a callback that will be triggered every time a barrier is tripped.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -8,11 +8,6 @@ package com.iluwatar.callback {
interface Callback { interface Callback {
+ call() {abstract} + call() {abstract}
} }
class LambdasApp {
- LOGGER : Logger {static}
- LambdasApp()
+ main(args : String[]) {static}
}
class SimpleTask { class SimpleTask {
- LOGGER : Logger {static} - LOGGER : Logger {static}
+ SimpleTask() + SimpleTask()

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>callback</artifactId> <artifactId>callback</artifactId>
<dependencies> <dependencies>

View File

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

View File

@ -9,27 +9,32 @@ tags:
--- ---
## Intent ## Intent
Avoid coupling the sender of a request to its receiver by giving Avoid coupling the sender of a request to its receiver by giving more than one object a chance to
more than one object a chance to handle the request. Chain the receiving handle the request. Chain the receiving objects and pass the request along the chain until an object
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 officer and then soldier. The commander, officer and soldier here form a chain of responsibility. > The Orc King gives loud orders to his army. The closest one to react is the commander, then
> officer and then soldier. The commander, officer and soldier here form a chain of responsibility.
In plain words In plain words
> It helps building a chain of objects. Request enters from one end and keeps going from object to object till it finds the suitable handler. > It helps to build a chain of objects. A request enters from one end and keeps going from an object
> to another until it finds a suitable handler.
Wikipedia says Wikipedia says
> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. > In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of
> a source of command objects and a series of processing objects. Each processing object contains
> logic that defines the types of command objects that it can handle; the rest are passed to the
> next processing object in the chain.
**Programmatic Example** **Programmatic Example**
Translating our example with 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 {
@ -140,14 +145,16 @@ king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc so
``` ```
## Class diagram ## Class diagram
![alt text](./etc/chain.urm.png "Chain of Responsibility class diagram") ![alt text](./etc/chain.urm.png "Chain of Responsibility class diagram")
## Applicability ## Applicability
Use Chain of Responsibility when Use Chain of Responsibility when
* more than one object may handle a request, and the handler isn't known a priori. The handler should be ascertained automatically * More than one object may handle a request, and the handler isn't known a priori. The handler should be ascertained automatically.
* 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.
## Real world examples ## Real world examples

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>chain</artifactId> <artifactId>chain</artifactId>
<dependencies> <dependencies>

View File

@ -25,13 +25,23 @@ package com.iluwatar.chain;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -12,32 +12,43 @@ tags:
## Intent ## Intent
Handle costly remote *procedure/service* calls in such a way that the failure of a **single** service/component cannot bring the whole application down, and we can reconnect to the service as soon as possible. Handle costly remote service calls in such a way that the failure of a single service/component
cannot bring the whole application down, and we can reconnect to the service as soon as possible.
## Explanation ## Explanation
Real world example Real world example
> Imagine a Web App that has both local (example: files and images) and remote (example: database entries) to serve. The database might not be responding due to a variety of reasons, so if the application keeps trying to read from the database using multiple threads/processes, soon all of them will hang and our entire web application will crash. We should be able to detect this situation and show the user an appropriate message so that he/she can explore other parts of the app unaffected by the database failure without any problem. > Imagine a web application that has both local files/images and remote database entries to serve.
> The database might not be responding due to a variety of reasons, so if the application keeps
> trying to read from the database using multiple threads/processes, soon all of them will hang
> causing our entire web application will crash. We should be able to detect this situation and show
> the user an appropriate message so that he/she can explore other parts of the app unaffected by
> the database failure.
In plain words In plain words
> Allows us to save resources when we know a remote service failed. Useful when all parts of our application are highly decoupled from each other, and failure of one component doesn't mean the other parts will stop working. > Circuit Breaker allows graceful handling of failed remote services. It's especially useful when
> all parts of our application are highly decoupled from each other, and failure of one component
> doesn't mean the other parts will stop working.
Wikipedia says Wikipedia says
> **Circuit breaker** is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties. > Circuit breaker is a design pattern used in modern software development. It is used to detect
> failures and encapsulates the logic of preventing a failure from constantly recurring, during
So, how does this all come together? > maintenance, temporary external system failure or unexpected system difficulties.
## Programmatic Example ## Programmatic Example
With the above example in mind we will imitate the functionality in a simple manner. We have two services: A *monitoring service* which will mimic the web app and will make both **local** and **remote** calls.
So, how does this all come together? With the above example in mind we will imitate the
functionality in a simple example. A monitoring service mimics the web app and makes both local and
remote calls.
The service architecture is as follows: The service architecture is as follows:
![alt text](./etc/ServiceDiagram.PNG "Service Diagram") ![alt text](./etc/ServiceDiagram.PNG "Service Diagram")
In terms of code, the End user application is: In terms of code, the end user application is:
```java ```java
public class App { public class App {
@ -62,7 +73,7 @@ public class App {
} }
``` ```
The monitoring service is: The monitoring service:
``` java ``` java
public class MonitoringService { public class MonitoringService {
@ -80,7 +91,8 @@ public class MonitoringService {
} }
} }
``` ```
As it can be seen, it does the call to get local resources directly, but it wraps the call to remote (costly) service in a circuit breaker object, which prevents faults as follows: As it can be seen, it does the call to get local resources directly, but it wraps the call to
remote (costly) service in a circuit breaker object, which prevents faults as follows:
```java ```java
public class CircuitBreaker { public class CircuitBreaker {
@ -155,24 +167,27 @@ public class CircuitBreaker {
} }
``` ```
How does the above pattern prevent failures? Let's understand via this finite state machine implemented by it. How does the above pattern prevent failures? Let's understand via this finite state machine
implemented by it.
![alt text](./etc/StateDiagram.PNG "State Diagram") ![alt text](./etc/StateDiagram.PNG "State Diagram")
- We initialize the Circuit Breaker object with certain parameters: **timeout**, **failureThreshold** and **retryTimePeriod** which help determine how resilient the API is. - We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod` which help determine how resilient the API is.
- Initially, we are in the **closed** state and the remote call to API happens. - Initially, we are in the `closed` state and nos remote calls to the API have occurred.
- Every time the call succeeds, we reset the state to as it was in the beginning. - Every time the call succeeds, we reset the state to as it was in the beginning.
- If the number of failures cross a certain threshold, we move to the **open** state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```) - If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```)
- Once we exceed the retry timeout period, we move to the **half-open** state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A *failure* sets it back to **open** state and another attempt is made after retry timeout period, while a *success* sets it to **closed** state so that everything starts working normally again. - Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything starts working normally again.
## Class diagram ## Class diagram
![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram") ![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram")
## Applicability ## Applicability
Use the Circuit Breaker pattern when Use the Circuit Breaker pattern when
- Building a fault-tolerant application where failure of some services shouldn't bring the entire application down. - Building a fault-tolerant application where failure of some services shouldn't bring the entire application down.
- Building an continuously incremental/continuous delivery application, as some of it's components can be upgraded without shutting it down entirely. - Building a continuously running (always-on) application, so that its components can be upgraded without shutting it down entirely.
## Related Patterns ## Related Patterns

View File

@ -27,7 +27,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>circuit-breaker</artifactId> <artifactId>circuit-breaker</artifactId>
<dependencies> <dependencies>

View File

@ -27,7 +27,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>collection-pipeline</artifactId> <artifactId>collection-pipeline</artifactId>
<dependencies> <dependencies>

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>combinator</artifactId> <artifactId>combinator</artifactId>
@ -39,6 +39,12 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -25,12 +25,19 @@ package com.iluwatar.combinator;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class CombinatorAppTest { public class CombinatorAppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])}
* throws an exception.
*/
@Test @Test
public void main() { public void shouldExecuteApplicationWithoutException() {
CombinatorApp.main(new String[]{}); assertDoesNotThrow(() -> CombinatorApp.main(new String[]{}));
} }
} }

View File

@ -9,15 +9,20 @@ tags:
--- ---
## Also known as ## Also known as
Action, Transaction Action, Transaction
## Intent ## Intent
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Encapsulate a request as an object, thereby letting you parameterize clients with different
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. The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses the spells one by one. Each spell here is a command object that can be undone. > 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 spells one by one. Each spell here is a command object that can be undone.
In plain words In plain words
@ -25,11 +30,13 @@ In plain words
Wikipedia says Wikipedia says
> In object-oriented programming, 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. > In object-oriented programming, 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.
**Programmatic Example** **Programmatic Example**
Here's the sample code with wizard and goblin. Let's start from the wizard class. Here's the sample code with wizard and goblin. Let's start from the `Wizard` class.
```java ```java
public class Wizard { public class Wizard {
@ -149,7 +156,7 @@ public class ShrinkSpell implements Command {
} }
``` ```
And last we have the goblin who's the target of the spells. Finally, we have the goblin who's the target of the spells.
```java ```java
public abstract class Target { public abstract class Target {
@ -199,44 +206,67 @@ public class Goblin extends Target {
} }
``` ```
Finally here's the whole example in action. Here's the whole example in action.
```java ```java
var wizard = new Wizard(); var wizard = new Wizard();
var goblin = new Goblin(); var goblin = new Goblin();
goblin.printStatus(); goblin.printStatus();
// Goblin, [size=normal] [visibility=visible]
wizard.castSpell(new ShrinkSpell(), goblin); wizard.castSpell(new ShrinkSpell(), goblin);
// Wizard casts Shrink spell at Goblin
goblin.printStatus(); goblin.printStatus();
// Goblin, [size=small] [visibility=visible]
wizard.castSpell(new InvisibilitySpell(), goblin); wizard.castSpell(new InvisibilitySpell(), goblin);
// Wizard casts Invisibility spell at Goblin
goblin.printStatus(); goblin.printStatus();
// Goblin, [size=small] [visibility=invisible]
wizard.undoLastSpell(); wizard.undoLastSpell();
// Wizard undoes Invisibility spell
goblin.printStatus(); goblin.printStatus();
```
Here's the program output:
```java
// Goblin, [size=normal] [visibility=visible]
// Wizard casts Shrink spell at Goblin
// Goblin, [size=small] [visibility=visible]
// Wizard casts Invisibility spell at Goblin
// Goblin, [size=small] [visibility=invisible]
// Wizard undoes Invisibility spell
// Goblin, [size=small] [visibility=visible] // Goblin, [size=small] [visibility=visible]
``` ```
## Class diagram ## Class diagram
![alt text](./etc/command.png "Command") ![alt text](./etc/command.png "Command")
## Applicability ## Applicability
Use the Command pattern when you want to
* 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 called at a later point. Commands are an object-oriented replacement for callbacks. Use the Command pattern when you want to:
* 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 space-independent way, then you can transfer a command object for the request to a different process and fulfill the request there
* 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 Unexecute operation that reverses the effects of a previous call to execute. Executed commands are stored in a history list. Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling unexecute and execute, respectively * Parameterize objects by an action to perform. You can express such parameterization in a
* 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. Recovering from a crash involves reloading logged commands from disk and re-executing them with the execute operation procedural language with a callback function, that is, a function that's registered somewhere to be
* 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 changes 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 system with new transactions 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 lifetime
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
and fulfill the request there.
* 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
effects of a previous call to execute. The executed commands are stored in a history list.
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
un-execute and execute, respectively.
* 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.
Recovering from a crash involves reloading logged commands from disk and re-executing them with
the execute operation.
* 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 changes
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
system with new transactions.
## Typical Use Case ## Typical Use Case
* to keep a history of requests * To keep a history of requests
* implement callback functionality * Implement callback functionality
* implement the undo functionality * Implement the undo functionality
## Real world examples ## Real world examples

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>command</artifactId> <artifactId>command</artifactId>
<dependencies> <dependencies>

View File

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

View File

@ -27,7 +27,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>commander</artifactId> <artifactId>commander</artifactId>
<dependencies> <dependencies>

View File

@ -36,9 +36,11 @@ import com.iluwatar.commander.queue.QueueDatabase;
import com.iluwatar.commander.queue.QueueTask; import com.iluwatar.commander.queue.QueueTask;
import com.iluwatar.commander.queue.QueueTask.TaskType; import com.iluwatar.commander.queue.QueueTask.TaskType;
import com.iluwatar.commander.shippingservice.ShippingService; import com.iluwatar.commander.shippingservice.ShippingService;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* <p>Commander pattern is used to handle all issues that can come up while making a * <p>Commander pattern is used to handle all issues that can come up while making a
* distributed transaction. The idea is to have a commander, which coordinates the execution of all * distributed transaction. The idea is to have a commander, which coordinates the execution of all
@ -159,8 +161,8 @@ public class Commander {
private void sendPaymentRequest(Order order) { private void sendPaymentRequest(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) { if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) {
if (order.paid.equals(PaymentStatus.Trying)) { if (order.paid.equals(PaymentStatus.TRYING)) {
order.paid = PaymentStatus.NotDone; order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(order); sendPaymentFailureMessage(order);
LOG.error("Order " + order.id + ": Payment time for order over, failed and returning.."); LOG.error("Order " + order.id + ": Payment time for order over, failed and returning..");
} //if succeeded or failed, would have been dequeued, no attempt to make payment } //if succeeded or failed, would have been dequeued, no attempt to make payment
@ -172,15 +174,15 @@ public class Commander {
if (!l.isEmpty()) { if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to payment service," LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
+ " trying again.."); + " trying again..");
} else { } else {
LOG.debug("Order " + order.id + ": Error in creating payment request.."); LOG.debug("Order " + order.id + ": Error in creating payment request..");
} }
throw l.remove(0); throw l.remove(0);
} }
if (order.paid.equals(PaymentStatus.Trying)) { if (order.paid.equals(PaymentStatus.TRYING)) {
var transactionId = paymentService.receiveRequest(order.price); var transactionId = paymentService.receiveRequest(order.price);
order.paid = PaymentStatus.Done; order.paid = PaymentStatus.DONE;
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId); LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
if (!finalSiteMsgShown) { if (!finalSiteMsgShown) {
LOG.info("Payment made successfully, thank you for shopping with us!!"); LOG.info("Payment made successfully, thank you for shopping with us!!");
@ -193,26 +195,26 @@ public class Commander {
if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) { if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) {
if (!finalSiteMsgShown) { if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. Your account/card details " LOG.info("There was an error in payment. Your account/card details "
+ "may have been incorrect. " + "may have been incorrect. "
+ "Meanwhile, your order has been converted to COD and will be shipped."); + "Meanwhile, your order has been converted to COD and will be shipped.");
finalSiteMsgShown = true; finalSiteMsgShown = true;
} }
LOG.error("Order " + order.id + ": Payment details incorrect, failed.."); LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
o.paid = PaymentStatus.NotDone; o.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(o); sendPaymentFailureMessage(o);
} else { } else {
if (o.messageSent.equals(MessageSent.NoneSent)) { if (o.messageSent.equals(MessageSent.NONE_SENT)) {
if (!finalSiteMsgShown) { if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. We are on it, and will get back to you " LOG.info("There was an error in payment. We are on it, and will get back to you "
+ "asap. Don't worry, your order has been placed and will be shipped."); + "asap. Don't worry, your order has been placed and will be shipped.");
finalSiteMsgShown = true; finalSiteMsgShown = true;
} }
LOG.warn("Order " + order.id + ": Payment error, going to queue.."); LOG.warn("Order " + order.id + ": Payment error, going to queue..");
sendPaymentPossibleErrorMsg(o); sendPaymentPossibleErrorMsg(o);
} }
if (o.paid.equals(PaymentStatus.Trying) && System if (o.paid.equals(PaymentStatus.TRYING) && System
.currentTimeMillis() - o.createdTime < paymentTime) { .currentTimeMillis() - o.createdTime < paymentTime) {
var qt = new QueueTask(o, TaskType.Payment, -1); var qt = new QueueTask(o, TaskType.PAYMENT, -1);
updateQueue(qt); updateQueue(qt);
} }
} }
@ -234,12 +236,12 @@ public class Commander {
// additional check not needed // additional check not needed
LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed.."); LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed..");
return; return;
} else if (qt.taskType.equals(TaskType.Payment) && !qt.order.paid.equals(PaymentStatus.Trying) } else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|| qt.taskType.equals(TaskType.Messaging) && (qt.messageType == 1 || qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
&& !qt.order.messageSent.equals(MessageSent.NoneSent) && !qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| qt.order.messageSent.equals(MessageSent.PaymentFail) || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|| qt.taskType.equals(TaskType.EmployeeDb) && qt.order.addedToEmployeeHandle) { || qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done.."); LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done..");
return; return;
} }
@ -256,8 +258,8 @@ public class Commander {
tryDoingTasksInQueue(); tryDoingTasksInQueue();
}; };
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> { Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
if (qt1.taskType.equals(TaskType.Payment)) { if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NotDone; qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order); sendPaymentFailureMessage(qt1.order);
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task," LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
+ " payment failed.."); + " payment failed..");
@ -331,35 +333,9 @@ public class Commander {
} }
var list = messagingService.exceptionsList; var list = messagingService.exceptionsList;
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
Retry.Operation op = (l) -> { Retry.Operation op = handleSuccessMessageRetryOperation(order);
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PaymentFail)
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PaymentSuccessful;
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> { Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent handleSuccessMessageErrorIssue(order, o);
.equals(MessageSent.PaymentTrying))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 2);
updateQueue(qt);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
}; };
var r = new Retry<>(op, handleError, numOfRetries, retryDuration, var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -372,6 +348,40 @@ public class Commander {
t.start(); t.start();
} }
private void handleSuccessMessageErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
updateQueue(qt);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
}
private Retry.Operation handleSuccessMessageRetryOperation(Order order) {
return (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
}
private void sendPaymentFailureMessage(Order order) { private void sendPaymentFailureMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace("Order " + order.id + ": Message time for order over, returning.."); LOG.trace("Order " + order.id + ": Message time for order over, returning..");
@ -380,34 +390,10 @@ public class Commander {
var list = messagingService.exceptionsList; var list = messagingService.exceptionsList;
var t = new Thread(() -> { var t = new Thread(() -> {
Retry.Operation op = (l) -> { Retry.Operation op = (l) -> {
if (!l.isEmpty()) { handlePaymentFailureRetryOperation(order, l);
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PaymentFail)
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PaymentFail;
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
}; };
Retry.HandleErrorIssue<Order> handleError = (o, err) -> { Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent handlePaymentErrorIssue(order, o);
.equals(MessageSent.PaymentTrying))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 0);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}; };
var r = new Retry<>(op, handleError, numOfRetries, retryDuration, var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -420,6 +406,38 @@ public class Commander {
t.start(); t.start();
} }
private void handlePaymentErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PAYMENT_FAIL;
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
}
private void sendPaymentPossibleErrorMsg(Order order) { private void sendPaymentPossibleErrorMsg(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace("Message time for order over, returning.."); LOG.trace("Message time for order over, returning..");
@ -428,34 +446,10 @@ public class Commander {
var list = messagingService.exceptionsList; var list = messagingService.exceptionsList;
var t = new Thread(() -> { var t = new Thread(() -> {
Retry.Operation op = (l) -> { Retry.Operation op = (l) -> {
if (!l.isEmpty()) { handlePaymentPossibleErrorMsgRetryOperation(order, l);
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.Trying) && order.messageSent
.equals(MessageSent.NoneSent)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PaymentTrying;
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
}; };
Retry.HandleErrorIssue<Order> handleError = (o, err) -> { Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (o.messageSent.equals(MessageSent.NoneSent) && order.paid handlePaymentPossibleErrorMsgErrorIssue(order, o);
.equals(PaymentStatus.Trying)
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}; };
var r = new Retry<>(op, handleError, numOfRetries, retryDuration, var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -468,6 +462,39 @@ public class Commander {
t.start(); t.start();
} }
private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) {
if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid
.equals(PaymentStatus.TRYING)
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List<Exception> l)
throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent
.equals(MessageSent.NONE_SENT)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PAYMENT_TRYING;
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
}
private void employeeHandleIssue(Order order) { private void employeeHandleIssue(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) { if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning.."); LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
@ -490,7 +517,7 @@ public class Commander {
Retry.HandleErrorIssue<Order> handleError = (o, err) -> { Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (!o.addedToEmployeeHandle && System if (!o.addedToEmployeeHandle && System
.currentTimeMillis() - order.createdTime < employeeTime) { .currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EmployeeDb, -1); var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt); updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in adding to employee db," LOG.warn("Order " + order.id + ": Error in adding to employee db,"
+ " trying to queue task.."); + " trying to queue task..");
@ -520,21 +547,21 @@ public class Commander {
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType() LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
+ " does not need to be done anymore (timeout), dequeue.."); + " does not need to be done anymore (timeout), dequeue..");
} else { } else {
if (qt.taskType.equals(TaskType.Payment)) { if (qt.taskType.equals(TaskType.PAYMENT)) {
if (!qt.order.paid.equals(PaymentStatus.Trying)) { if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
tryDequeue(); tryDequeue();
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing.."); LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
} else { } else {
sendPaymentRequest(qt.order); sendPaymentRequest(qt.order);
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service.."); LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
} }
} else if (qt.taskType.equals(TaskType.Messaging)) { } else if (qt.taskType.equals(TaskType.MESSAGING)) {
if (qt.order.messageSent.equals(MessageSent.PaymentFail) if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) { || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
tryDequeue(); tryDequeue();
LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue.."); LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue..");
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NoneSent) } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.Trying))) { || !qt.order.paid.equals(PaymentStatus.TRYING))) {
tryDequeue(); tryDequeue();
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done," LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
+ " dequeue.."); + " dequeue..");
@ -548,7 +575,7 @@ public class Commander {
sendSuccessMessage(qt.order); sendSuccessMessage(qt.order);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
} }
} else if (qt.taskType.equals(TaskType.EmployeeDb)) { } else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
if (qt.order.addedToEmployeeHandle) { if (qt.order.addedToEmployeeHandle) {
tryDequeue(); tryDequeue();
LOG.trace("Order " + qt.order.id + ": This employee handle task already done," LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"

View File

@ -33,11 +33,11 @@ import java.util.Random;
public class Order { //can store all transactions ids also public class Order { //can store all transactions ids also
enum PaymentStatus { enum PaymentStatus {
NotDone, Trying, Done NOT_DONE, TRYING, DONE
} }
enum MessageSent { enum MessageSent {
NoneSent, PaymentFail, PaymentTrying, PaymentSuccessful NONE_SENT, PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
} }
final User user; final User user;
@ -65,8 +65,8 @@ public class Order { //can store all transactions ids also
} }
this.id = id; this.id = id;
USED_IDS.put(this.id, true); USED_IDS.put(this.id, true);
this.paid = PaymentStatus.Trying; this.paid = PaymentStatus.TRYING;
this.messageSent = MessageSent.NoneSent; this.messageSent = MessageSent.NONE_SENT;
this.addedToEmployeeHandle = false; this.addedToEmployeeHandle = false;
} }

View File

@ -38,7 +38,7 @@ public class MessagingService extends Service {
private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class); private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class);
enum MessageToSend { enum MessageToSend {
PaymentFail, PaymentTrying, PaymentSuccessful PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
} }
class MessageRequest { class MessageRequest {
@ -63,11 +63,11 @@ public class MessagingService extends Service {
var id = generateId(); var id = generateId();
MessageToSend msg; MessageToSend msg;
if (messageToSend == 0) { if (messageToSend == 0) {
msg = MessageToSend.PaymentFail; msg = MessageToSend.PAYMENT_FAIL;
} else if (messageToSend == 1) { } else if (messageToSend == 1) {
msg = MessageToSend.PaymentTrying; msg = MessageToSend.PAYMENT_TRYING;
} else { //messageToSend == 2 } else { //messageToSend == 2
msg = MessageToSend.PaymentSuccessful; msg = MessageToSend.PAYMENT_SUCCESSFUL;
} }
var req = new MessageRequest(id, msg); var req = new MessageRequest(id, msg);
return updateDb(req); return updateDb(req);
@ -84,10 +84,10 @@ public class MessagingService extends Service {
} }
String sendMessage(MessageToSend m) { String sendMessage(MessageToSend m) {
if (m.equals(MessageToSend.PaymentSuccessful)) { if (m.equals(MessageToSend.PAYMENT_SUCCESSFUL)) {
return "Msg: Your order has been placed and paid for successfully!" return "Msg: Your order has been placed and paid for successfully!"
+ " Thank you for shopping with us!"; + " Thank you for shopping with us!";
} else if (m.equals(MessageToSend.PaymentTrying)) { } else if (m.equals(MessageToSend.PAYMENT_TRYING)) {
return "Msg: There was an error in your payment process," return "Msg: There was an error in your payment process,"
+ " we are working on it and will return back to you shortly." + " we are working on it and will return back to you shortly."
+ " Meanwhile, your order has been placed and will be shipped."; + " Meanwhile, your order has been placed and will be shipped.";

View File

@ -36,7 +36,7 @@ public class QueueTask {
*/ */
public enum TaskType { public enum TaskType {
Messaging, Payment, EmployeeDb MESSAGING, PAYMENT, EMPLOYEE_DB
} }
public Order order; public Order order;
@ -68,7 +68,7 @@ public class QueueTask {
* @return String representing type of task * @return String representing type of task
*/ */
public String getType() { public String getType() {
if (!this.taskType.equals(TaskType.Messaging)) { if (!this.taskType.equals(TaskType.MESSAGING)) {
return this.taskType.toString(); return this.taskType.toString();
} else { } else {
if (this.messageType == 0) { if (this.messageType == 0) {

View File

@ -9,15 +9,17 @@ tags:
--- ---
## Intent ## Intent
Compose objects into tree structures to represent part-whole
hierarchies. Composite lets clients treat individual objects and compositions Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients
of objects uniformly. 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 objects is printable and they can have something printed before or after them like sentence always ends with full stop and word always has space before it > Every sentence is composed of words which are in turn composed of characters. Each of these
> objects is printable and they can have something printed before or after them like sentence always
> ends with full stop and word always has space before it.
In plain words In plain words
@ -25,11 +27,16 @@ In plain words
Wikipedia says Wikipedia says
> In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. > In software engineering, the composite pattern is a partitioning design pattern. The composite
> pattern describes that a group of objects is to be treated in the same way as a single instance of
> an object. The intent of a composite is to "compose" objects into tree structures to represent
> part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects
> and compositions uniformly.
**Programmatic Example** **Programmatic Example**
Taking our sentence example from above. Here we have the base class and different printable types Taking our sentence example from above. Here we have the base class `LetterComposite` and the
different printable types `Letter`, `Word` and `Sentence`.
```java ```java
public abstract class LetterComposite { public abstract class LetterComposite {
@ -102,7 +109,7 @@ public class Sentence extends LetterComposite {
} }
``` ```
Then we have a messenger to carry messages Then we have a messenger to carry messages:
```java ```java
public class Messenger { public class Messenger {
@ -143,7 +150,7 @@ public class Messenger {
} }
``` ```
And then it can be used as And then it can be used as:
```java ```java
var orcMessage = new Messenger().messageFromOrcs(); var orcMessage = new Messenger().messageFromOrcs();
@ -153,13 +160,16 @@ elfMessage.print(); // Much wind pours from your mouth.
``` ```
## Class diagram ## Class diagram
![alt text](./etc/composite.urm.png "Composite class diagram") ![alt text](./etc/composite.urm.png "Composite class diagram")
## Applicability ## Applicability
Use the Composite pattern when Use the Composite pattern when
* you want to represent part-whole hierarchies of objects * You want to represent part-whole hierarchies of objects.
* 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 * 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.
## Real world examples ## Real world examples

View File

@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>composite</artifactId> <artifactId>composite</artifactId>
<dependencies> <dependencies>

View File

@ -23,15 +23,23 @@
package com.iluwatar.composite; package com.iluwatar.composite;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** /**
* Application test * Application test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); Assertions.assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -9,16 +9,19 @@ tags:
--- ---
## Intent ## Intent
The purpose of the Converter Pattern is to provide a generic, common way of bidirectional
The purpose of the Converter pattern is to provide a generic, common way of bidirectional
conversion between corresponding types, allowing a clean implementation in which the types do not conversion between corresponding types, allowing a clean implementation in which the types do not
need to be aware of each other. Moreover, the Converter Pattern introduces bidirectional collection need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection
mapping, reducing a boilerplate code to minimum. mapping, reducing a boilerplate code to minimum.
## Explanation ## Explanation
Real world example Real world example
> In real world applications it is often the case that database layer consists of entities that need to be mapped into DTOs for use on the business logic layer. Similar mapping is done for potentially huge amount of classes and we need a generic way to achieve this. > In real world applications it is often the case that database layer consists of entities that need
> to be mapped into DTOs for use on the business logic layer. Similar mapping is done for
> potentially huge amount of classes and we need a generic way to achieve this.
In plain words In plain words
@ -26,7 +29,8 @@ In plain words
**Programmatic Example** **Programmatic Example**
We need a generic solution for the mapping problem. To achieve this, let's introduce a generic converter. We need a generic solution for the mapping problem. To achieve this, let's introduce a generic
converter.
```java ```java
public class Converter<T, U> { public class Converter<T, U> {
@ -77,7 +81,7 @@ public class UserConverter extends Converter<UserDto, User> {
} }
``` ```
Now mapping between User and UserDto becomes trivial. Now mapping between `User` and `UserDto` becomes trivial.
```java ```java
var userConverter = new UserConverter(); var userConverter = new UserConverter();
@ -86,14 +90,18 @@ var user = userConverter.convertFromDto(dtoUser);
``` ```
## Class diagram ## Class diagram
![alt text](./etc/converter.png "Converter Pattern") ![alt text](./etc/converter.png "Converter Pattern")
## Applicability ## Applicability
Use the Converter Pattern in the following situations: Use the Converter Pattern in the following situations:
* When you have types that logically correspond which other and you need to convert entities between them * When you have types that logically correspond with each other and you need to convert entities
* When you want to provide different ways of types conversions depending on a context between them.
* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the domain equivalence * When you want to provide different ways of types conversions depending on the context.
* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the
domain equivalence.
## Credits ## Credits

View File

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>converter</artifactId> <artifactId>converter</artifactId>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -25,14 +25,24 @@ package com.iluwatar.converter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* App running test * App running test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test @Test
public void testMain() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -30,7 +30,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>cqrs</artifactId> <artifactId>cqrs</artifactId>
<dependencies> <dependencies>

View File

@ -9,13 +9,15 @@ tags:
--- ---
## Intent ## Intent
Object provides an abstract interface to some type of database or other persistence mechanism. Object provides an abstract interface to some type of database or other persistence mechanism.
## Explanation ## Explanation
Real world example Real world example
> There's a set of customers that need to be persisted to database. Additionally we need the whole set of CRUD (create/read/update/delete) operations so we can operate on customers easily. > There's a set of customers that need to be persisted to database. Additionally we need the whole
> set of CRUD (create/read/update/delete) operations so we can operate on customers easily.
In plain words In plain words
@ -23,11 +25,12 @@ In plain words
Wikipedia says Wikipedia says
> In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism. > In computer software, a data access object (DAO) is a pattern that provides an abstract interface
> to some type of database or other persistence mechanism.
**Programmatic Example** **Programmatic Example**
Walking through our customers example, here's the basic Customer entity. Walking through our customers example, here's the basic `Customer` entity.
```java ```java
public class Customer { public class Customer {
@ -41,60 +44,13 @@ public class Customer {
this.firstName = firstName; this.firstName = firstName;
this.lastName = lastName; this.lastName = lastName;
} }
// getters and setters ->
public int getId() { ...
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "Customer{" + "id=" + getId() + ", firstName='" + getFirstName() + '\'' + ", lastName='"
+ getLastName() + '\'' + '}';
}
@Override
public boolean equals(final Object that) {
var isEqual = false;
if (this == that) {
isEqual = true;
} else if (that != null && getClass() == that.getClass()) {
final var customer = (Customer) that;
if (getId() == customer.getId()) {
isEqual = true;
}
}
return isEqual;
}
@Override
public int hashCode() {
return getId();
}
} }
``` ```
Here's the DAO interface and two different implementations for it. InMemoryCustomerDao keeps a simple map of customers Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao`
in memory while DBCustomerDao is the real RDBMS implementation. keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation.
```java ```java
public interface CustomerDao { public interface CustomerDao {
@ -114,35 +70,8 @@ public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>(); private final Map<Integer, Customer> idToCustomer = new HashMap<>();
@Override // implement the interface using the map
public Stream<Customer> getAll() { ...
return idToCustomer.values().stream();
}
@Override
public Optional<Customer> getById(final int id) {
return Optional.ofNullable(idToCustomer.get(id));
}
@Override
public boolean add(final Customer customer) {
if (getById(customer.getId()).isPresent()) {
return false;
}
idToCustomer.put(customer.getId(), customer);
return true;
}
@Override
public boolean update(final Customer customer) {
return idToCustomer.replace(customer.getId(), customer) != null;
}
@Override
public boolean delete(final Customer customer) {
return idToCustomer.remove(customer.getId()) != null;
}
} }
public class DbCustomerDao implements CustomerDao { public class DbCustomerDao implements CustomerDao {
@ -155,121 +84,8 @@ public class DbCustomerDao implements CustomerDao {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
@Override // implement the interface using the data source
public Stream<Customer> getAll() throws Exception { ...
try {
var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS");
var resultSet = statement.executeQuery();
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Customer>(Long.MAX_VALUE,
Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Customer> action) {
try {
if (!resultSet.next()) {
return false;
}
action.accept(createCustomer(resultSet));
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}, false).onClose(() -> mutedClose(connection, statement, resultSet));
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) {
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
}
private Customer createCustomer(ResultSet resultSet) throws SQLException {
return new Customer(resultSet.getInt("ID"),
resultSet.getString("FNAME"),
resultSet.getString("LNAME"));
}
@Override
public Optional<Customer> getById(int id) throws Exception {
ResultSet resultSet = null;
try (var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, id);
resultSet = statement.executeQuery();
if (resultSet.next()) {
return Optional.of(createCustomer(resultSet));
} else {
return Optional.empty();
}
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
@Override
public boolean add(Customer customer) throws Exception {
if (getById(customer.getId()).isPresent()) {
return false;
}
try (var connection = getConnection();
var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) {
statement.setInt(1, customer.getId());
statement.setString(2, customer.getFirstName());
statement.setString(3, customer.getLastName());
statement.execute();
return true;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
@Override
public boolean update(Customer customer) throws Exception {
try (var connection = getConnection();
var statement =
connection
.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) {
statement.setString(1, customer.getFirstName());
statement.setString(2, customer.getLastName());
statement.setInt(3, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
@Override
public boolean delete(Customer customer) throws Exception {
try (var connection = getConnection();
var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
}
``` ```
Finally here's how we use our DAO to manage customers. Finally here's how we use our DAO to manage customers.
@ -301,15 +117,45 @@ Finally here's how we use our DAO to manage customers.
deleteSchema(dataSource); deleteSchema(dataSource);
``` ```
The program output:
```java
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c
```
## Class diagram ## Class diagram
![alt text](./etc/dao.png "Data Access Object") ![alt text](./etc/dao.png "Data Access Object")
## Applicability ## Applicability
Use the Data Access Object in any of the following situations
* when you want to consolidate how the data layer is accessed Use the Data Access Object in any of the following situations:
* when you want to avoid writing multiple data retrieval/persistence layers
* When you want to consolidate how the data layer is accessed.
* When you want to avoid writing multiple data retrieval/persistence layers.
## Credits ## Credits

View File

@ -30,7 +30,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

View File

@ -25,12 +25,22 @@ package com.iluwatar.dao;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that DAO example runs without errors. * Tests that DAO example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test @Test
public void test() throws Exception { void shouldExecuteDaoWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

View File

@ -33,7 +33,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>data-bus</artifactId> <artifactId>data-bus</artifactId>
<dependencies> <dependencies>

View File

@ -30,7 +30,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>data-locality</artifactId> <artifactId>data-locality</artifactId>

View File

@ -43,6 +43,6 @@ public class AiComponent implements Component {
@Override @Override
public void render() { public void render() {
// Do Nothing.
} }
} }

View File

@ -40,7 +40,7 @@ public class AiComponentManager {
private final int numEntities; private final int numEntities;
private static final Component[] AI_COMPONENTS = new AiComponent[MAX_ENTITIES]; private final Component[] aiComponents = new AiComponent[MAX_ENTITIES];
public AiComponentManager(int numEntities) { public AiComponentManager(int numEntities) {
this.numEntities = numEntities; this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class AiComponentManager {
*/ */
public void start() { public void start() {
LOGGER.info("Start AI Game Component"); LOGGER.info("Start AI Game Component");
IntStream.range(0, numEntities).forEach(i -> AI_COMPONENTS[i] = new AiComponent()); IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent());
} }
/** /**
@ -60,7 +60,7 @@ public class AiComponentManager {
public void update() { public void update() {
LOGGER.info("Update AI Game Component"); LOGGER.info("Update AI Game Component");
IntStream.range(0, numEntities) IntStream.range(0, numEntities)
.filter(i -> AI_COMPONENTS.length > i && AI_COMPONENTS[i] != null) .filter(i -> aiComponents.length > i && aiComponents[i] != null)
.forEach(i -> AI_COMPONENTS[i].update()); .forEach(i -> aiComponents[i].update());
} }
} }

View File

@ -40,7 +40,7 @@ public class PhysicsComponentManager {
private final int numEntities; private final int numEntities;
private static final Component[] PHYSICS_COMPONENTS = new PhysicsComponent[MAX_ENTITIES]; private final Component[] physicsComponents = new PhysicsComponent[MAX_ENTITIES];
public PhysicsComponentManager(int numEntities) { public PhysicsComponentManager(int numEntities) {
this.numEntities = numEntities; this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class PhysicsComponentManager {
*/ */
public void start() { public void start() {
LOGGER.info("Start Physics Game Component "); LOGGER.info("Start Physics Game Component ");
IntStream.range(0, numEntities).forEach(i -> PHYSICS_COMPONENTS[i] = new PhysicsComponent()); IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent());
} }
@ -62,7 +62,7 @@ public class PhysicsComponentManager {
LOGGER.info("Update Physics Game Component "); LOGGER.info("Update Physics Game Component ");
// Process physics. // Process physics.
IntStream.range(0, numEntities) IntStream.range(0, numEntities)
.filter(i -> PHYSICS_COMPONENTS.length > i && PHYSICS_COMPONENTS[i] != null) .filter(i -> physicsComponents.length > i && physicsComponents[i] != null)
.forEach(i -> PHYSICS_COMPONENTS[i].update()); .forEach(i -> physicsComponents[i].update());
} }
} }

View File

@ -40,7 +40,7 @@ public class RenderComponentManager {
private final int numEntities; private final int numEntities;
private static final Component[] RENDER_COMPONENTS = new RenderComponent[MAX_ENTITIES]; private final Component[] renderComponents = new RenderComponent[MAX_ENTITIES];
public RenderComponentManager(int numEntities) { public RenderComponentManager(int numEntities) {
this.numEntities = numEntities; this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class RenderComponentManager {
*/ */
public void start() { public void start() {
LOGGER.info("Start Render Game Component "); LOGGER.info("Start Render Game Component ");
IntStream.range(0, numEntities).forEach(i -> RENDER_COMPONENTS[i] = new RenderComponent()); IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent());
} }
@ -62,7 +62,7 @@ public class RenderComponentManager {
LOGGER.info("Update Render Game Component "); LOGGER.info("Update Render Game Component ");
// Process Render. // Process Render.
IntStream.range(0, numEntities) IntStream.range(0, numEntities)
.filter(i -> RENDER_COMPONENTS.length > i && RENDER_COMPONENTS[i] != null) .filter(i -> renderComponents.length > i && renderComponents[i] != null)
.forEach(i -> RENDER_COMPONENTS[i].render()); .forEach(i -> renderComponents[i].render());
} }
} }

View File

@ -26,16 +26,22 @@ package com.iluwatar.data.locality;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Test Game Application * Test Game Application
*/ */
class ApplicationTest { class ApplicationTest {
/** /**
* Test run * Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])}
* throws an exception.
*/ */
@Test @Test
void main() { void shouldExecuteGameApplicationWithoutException() {
Application.main(new String[] {}); assertDoesNotThrow(() -> Application.main(new String[] {}));
} }
} }

View File

@ -28,7 +28,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>data-mapper</artifactId> <artifactId>data-mapper</artifactId>
<dependencies> <dependencies>

View File

@ -24,14 +24,25 @@
package com.iluwatar.datamapper; package com.iluwatar.datamapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Data-Mapper example runs without errors. * Tests that Data-Mapper example runs without errors.
*/ */
public final class AppTest { final class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main();
assertDoesNotThrow((Executable) App::main);
} }
} }

View File

@ -9,13 +9,16 @@ tags:
--- ---
## Intent ## Intent
Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to remote server.
Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to
remote server.
## Explanation ## Explanation
Real world example Real world example
> We need to fetch information about customers from remote database. Instead of querying the attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot. > We need to fetch information about customers from remote database. Instead of querying the
> attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot.
In plain words In plain words
@ -23,16 +26,17 @@ In plain words
Wikipedia says Wikipedia says
> In the field of programming a data transfer object (DTO) is an object that carries data between processes. The > In the field of programming a data transfer object (DTO) is an object that carries data between
motivation for its use is that communication between processes is usually done resorting to remote interfaces > processes. The motivation for its use is that communication between processes is usually done
(e.g., web services), where each call is an expensive operation. Because the majority of the cost of each call is > resorting to remote interfaces (e.g. web services), where each call is an expensive operation.
related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an > Because the majority of the cost of each call is related to the round-trip time between the client
object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by > and the server, one way of reducing the number of calls is to use an object (the DTO) that
one call only. > aggregates the data that would have been transferred by the several calls, but that is served by
> one call only.
**Programmatic Example** **Programmatic Example**
Let's first introduce our simple customer DTO class. Let's first introduce our simple `CustomerDTO` class.
```java ```java
public class CustomerDto { public class CustomerDto {
@ -60,7 +64,7 @@ public class CustomerDto {
} }
``` ```
Customer resource class acts as the server for customer information. `CustomerResource` class acts as the server for customer information.
```java ```java
public class CustomerResource { public class CustomerResource {
@ -94,10 +98,12 @@ Now fetching customer information is easy since we have the DTOs.
``` ```
## Class diagram ## Class diagram
![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") ![alt text](./etc/data-transfer-object.urm.png "data-transfer-object")
## Applicability ## Applicability
Use the Data Transfer Object pattern when
Use the Data Transfer Object pattern when:
* The client is asking for multiple information. And the information is related. * The client is asking for multiple information. And the information is related.
* When you want to boost the performance to get resources. * When you want to boost the performance to get resources.

View File

@ -28,7 +28,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>data-transfer-object</artifactId> <artifactId>data-transfer-object</artifactId>
<dependencies> <dependencies>

View File

@ -25,9 +25,19 @@ package com.iluwatar.datatransfer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class AppTest { import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test @Test
public void test() throws Exception { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }

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