mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-15 22:15:02 +00:00
Compare commits
97
Commits
ede37bd055
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cabb204f2 | ||
|
|
dd24a29f80 | ||
|
|
d8e3ad7898 | ||
|
|
8807d0f537 | ||
|
|
41625d8d35 | ||
|
|
6055caf34a | ||
|
|
3dad8220bb | ||
|
|
7c0abffe05 | ||
|
|
126ad5fa1f | ||
|
|
c8423198cd | ||
|
|
d7327917cf | ||
|
|
b0c34db084 | ||
|
|
aa11f6b6be | ||
|
|
5698dea49b | ||
|
|
4f28d4eb5b | ||
|
|
22a34127d0 | ||
|
|
8b079dba5c | ||
|
|
a563344a35 | ||
|
|
8c7a3105df | ||
|
|
29442249f8 | ||
|
|
1673930bc0 | ||
|
|
c298e94d96 | ||
|
|
d498332345 | ||
|
|
653bced988 | ||
|
|
5d6ff2f0c4 | ||
|
|
a7000fe61b | ||
|
|
47693507db | ||
|
|
f5bdf688f1 | ||
|
|
401c44b797 | ||
|
|
7e0ddb01cd | ||
|
|
f1dcddfd3a | ||
|
|
688095a4c7 | ||
|
|
b55fc2bcb5 | ||
|
|
74d2dbe0be | ||
|
|
f179974422 | ||
|
|
448c8b5d21 | ||
|
|
fcdf639b15 | ||
|
|
39dce8eedf | ||
|
|
9c5d703dda | ||
|
|
3b9a2df119 | ||
|
|
7b4482f08c | ||
|
|
3b758da41a | ||
|
|
0f67a61be1 | ||
|
|
5a2beb8808 | ||
|
|
4cc30055fc | ||
|
|
2dfb27c859 | ||
|
|
c317ce012a | ||
|
|
177dc52704 | ||
|
|
13cde46f2e | ||
|
|
c70b63d069 | ||
|
|
7473378ca2 | ||
|
|
d4a0ece9e3 | ||
|
|
1288720893 | ||
|
|
8b0cf692cf | ||
|
|
5ff41687df | ||
|
|
51b03f1137 | ||
|
|
4215c86de0 | ||
|
|
f5bd0380d5 | ||
|
|
a0a10b300b | ||
|
|
2c02289711 | ||
|
|
394aff8ac6 | ||
|
|
cd4753a660 | ||
|
|
bdc008fb78 | ||
|
|
93668590e5 | ||
|
|
a9ce57c4d0 | ||
|
|
f8d4dd8506 | ||
|
|
2bfd3e190d | ||
|
|
b0bd9e6593 | ||
|
|
e8595393d6 | ||
|
|
48ba55a8a6 | ||
|
|
b93a202d60 | ||
|
|
9e65023a9f | ||
|
|
9f0b7ae81d | ||
|
|
f93514af39 | ||
|
|
318130c486 | ||
|
|
f7ad42dc05 | ||
|
|
5a0440b696 | ||
|
|
546d9a5bae | ||
|
|
213a4a5ee7 | ||
|
|
83887271ae | ||
|
|
e4384ef7d2 | ||
|
|
d24dd22bb9 | ||
|
|
2d39fe7ed8 | ||
|
|
b8f819988f | ||
|
|
ce5154effc | ||
|
|
265e3d0bda | ||
|
|
a3fcc63167 | ||
|
|
dc3425dd0e | ||
|
|
7c12643b27 | ||
|
|
4caa0bfdbc | ||
|
|
f92e6829c3 | ||
|
|
fecf7e7a82 | ||
|
|
812e4cd6a2 | ||
|
|
3ba2560e09 | ||
|
|
08651d0a00 | ||
|
|
0ab60190ea | ||
|
|
98d3601b1e |
No files matched your search
+280
-1
@@ -2891,7 +2891,7 @@
|
|||||||
"login": "ved-asole",
|
"login": "ved-asole",
|
||||||
"name": "Ved Asole",
|
"name": "Ved Asole",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/77259804?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/77259804?v=4",
|
||||||
"profile": "http://vedasole.cloud",
|
"profile": "https://github.com/ved-asole",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
@@ -3530,6 +3530,285 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ronodhirSoumik",
|
||||||
|
"name": "Soumik Sarker",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/46843689?v=4",
|
||||||
|
"profile": "https://ronodhirsoumik.github.io",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "naman-sriv",
|
||||||
|
"name": "Naman Srivastava",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/82610773?v=4",
|
||||||
|
"profile": "https://github.com/naman-sriv",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "letdtcode",
|
||||||
|
"name": "Thanh Nguyen Duc",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/92111552?v=4",
|
||||||
|
"profile": "https://github.com/letdtcode",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "skamble2",
|
||||||
|
"name": "Soham Kamble",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/121136639?v=4",
|
||||||
|
"profile": "https://github.com/skamble2",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Olexandr88",
|
||||||
|
"name": "Olexandr88",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/93856062?v=4",
|
||||||
|
"profile": "https://github.com/Olexandr88",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "nilsreichardt",
|
||||||
|
"name": "Nils Reichardt",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/24459435?v=4",
|
||||||
|
"profile": "https://github.com/nilsreichardt",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "north-echo",
|
||||||
|
"name": "Christopher Lusk",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/122107484?v=4",
|
||||||
|
"profile": "http://www.northecho.dev",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "dagecko",
|
||||||
|
"name": "dagecko",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6371704?v=4",
|
||||||
|
"profile": "https://github.com/dagecko",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "jackmisbach",
|
||||||
|
"name": "jackmisbach",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/183796460?v=4",
|
||||||
|
"profile": "https://github.com/jackmisbach",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "avivdon",
|
||||||
|
"name": "avivdon",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/74790003?v=4",
|
||||||
|
"profile": "https://github.com/avivdon",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "quantdevv",
|
||||||
|
"name": "Aditya Kumawat",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/56085648?v=4",
|
||||||
|
"profile": "https://github.com/quantdevv",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "devch37",
|
||||||
|
"name": "devch37",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/72903623?v=4",
|
||||||
|
"profile": "https://github.com/devch37",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "HY-love-sleep",
|
||||||
|
"name": "HY-love-sleep",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/73268470?v=4",
|
||||||
|
"profile": "https://github.com/HY-love-sleep",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "CMD137",
|
||||||
|
"name": "CMD137",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/56242956?v=4",
|
||||||
|
"profile": "http://www.cmd137blog.top/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "QuantumRange",
|
||||||
|
"name": "QuantumRange",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/49843948?v=4",
|
||||||
|
"profile": "https://github.com/QuantumRange",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Dhiraj-birajdar",
|
||||||
|
"name": "Dhiraj Birajdar",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/114354331?v=4",
|
||||||
|
"profile": "https://github.com/Dhiraj-birajdar",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "sieberss",
|
||||||
|
"name": "Stefan Siebers",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/178474619?v=4",
|
||||||
|
"profile": "https://github.com/sieberss",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "orbisai0security",
|
||||||
|
"name": "OrbisAI Security",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/242526317?v=4",
|
||||||
|
"profile": "https://orbisappsec.com/",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "rranjangupta",
|
||||||
|
"name": "Raju Gupta",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/64011583?v=4",
|
||||||
|
"profile": "https://www.rajugupta.com/portfolio/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "alex052525",
|
||||||
|
"name": "김정모",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/116088682?v=4",
|
||||||
|
"profile": "https://github.com/alex052525",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "AnakinYen0329",
|
||||||
|
"name": "Anakin Yen",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/122139183?v=4",
|
||||||
|
"profile": "https://github.com/AnakinYen0329",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "SrGiuliano77",
|
||||||
|
"name": "Giuliano Oliveira",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/128543464?v=4",
|
||||||
|
"profile": "https://github.com/SrGiuliano77",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "priyanshuvishwakarma273403",
|
||||||
|
"name": "Priyanshu",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/222148451?v=4",
|
||||||
|
"profile": "https://www.priyanshu-portfolio.co.in",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "arnabnandy7",
|
||||||
|
"name": "Arnab Nandy",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/5053648?v=4",
|
||||||
|
"profile": "https://github.com/arnabnandy-cognizant",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "MohamedMBG",
|
||||||
|
"name": "BAGHDAD Mohamed",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/123114744?v=4",
|
||||||
|
"profile": "https://github.com/MohamedMBG",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "AnveshSrivastava",
|
||||||
|
"name": "Anvesh Srivastava",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/170334970?v=4",
|
||||||
|
"profile": "https://github.com/AnveshSrivastava",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Mukul-Howale",
|
||||||
|
"name": "Mukul Howale",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/110479646?v=4",
|
||||||
|
"profile": "https://github.com/Mukul-Howale",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "SandhyaDevadiga",
|
||||||
|
"name": "Sandhya",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/128058717?v=4",
|
||||||
|
"profile": "https://github.com/SandhyaDevadiga",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "devikae",
|
||||||
|
"name": "dev-ikae",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/98100047?v=4",
|
||||||
|
"profile": "https://github.com/devikae",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "zanarellidev",
|
||||||
|
"name": "zanarelli",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/268068569?v=4",
|
||||||
|
"profile": "https://github.com/zanarellidev",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "kaizencode-art",
|
||||||
|
"name": "KaizenCode",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/206610174?v=4",
|
||||||
|
"profile": "http://blog.kaizensphere.dev",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 6,
|
"contributorsPerLine": 6,
|
||||||
|
|||||||
@@ -51,3 +51,8 @@ jobs:
|
|||||||
# These two env variables are needed for sonar analysis
|
# These two env variables are needed for sonar analysis
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
name: Java PR Builder
|
name: Java PR Builder
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
types: [ opened, reopened, synchronize ]
|
types: [ opened, reopened, synchronize ]
|
||||||
|
|
||||||
@@ -17,8 +17,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -35,23 +33,14 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-maven-
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
# Cache Sonar packages which are used to run analysis and collect metrics
|
|
||||||
- name: Cache SonarCloud packages
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.sonar/cache
|
|
||||||
key: ${{ runner.os }}-sonar
|
|
||||||
restore-keys: ${{ runner.os }}-sonar
|
|
||||||
|
|
||||||
# Some tests need screen access
|
# Some tests need screen access
|
||||||
- name: Install xvfb
|
- name: Install xvfb
|
||||||
run: sudo apt-get install -y xvfb
|
run: sudo apt-get install -y xvfb
|
||||||
|
|
||||||
- name: Build with Maven and run SonarQube analysis
|
- name: Build with Maven
|
||||||
env:
|
run: xvfb-run ./mvnw clean verify
|
||||||
# Intermediate variable
|
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
- name: Upload coverage reports to Codecov
|
||||||
# These two env variables are needed for sonar analysis
|
uses: codecov/codecov-action@v5
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=iluwatar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.pullrequest.branch=$HEAD_REF -Dsonar.pullrequest.base=${{ github.base_ref }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
|
|
||||||
@@ -6,9 +6,9 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target: # Handle forked repository PRs in the base repository context
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
pull_request_review_comment: # Handle review comments
|
pull_request_review_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -21,15 +21,8 @@ jobs:
|
|||||||
echo "Error: LLM_API_KEY secret is not configured"
|
echo "Error: LLM_API_KEY secret is not configured"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
- uses: presubmit/ai-reviewer@latest
|
||||||
- name: Check out PR code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Run AI Reviewer
|
|
||||||
uses: presubmit/ai-reviewer@latest
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||||
LLM_MODEL: "gemini-1.5-flash"
|
LLM_MODEL: "gpt-5-nano"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Java Design Patterns - Priming Context for AI Agents
|
||||||
|
|
||||||
|
## Quick Overview
|
||||||
|
- This repository is a comprehensive collection of design patterns implemented in Java.
|
||||||
|
- The project demonstrates how to solve common software design problems using standard patterns.
|
||||||
|
- The code for each pattern lives directly in this repository, alongside its explanatory README.
|
||||||
|
- These readmes are published on the java-design-patterns.com website.
|
||||||
|
- Another repository (https://github.com/iluwatar/java-design-patterns-vuepress-web) handles the deployment of the website.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
- **Java 21**: The primary programming language used for pattern implementations.
|
||||||
|
- **Maven**: Dependency management and build tool.
|
||||||
|
- **JUnit 5**: The testing framework used to verify pattern behaviors.
|
||||||
|
- **Mockito**: Used for mocking dependencies in unit tests.
|
||||||
|
- **Lombok**: Used to reduce boilerplate code (getters, setters, etc.).
|
||||||
|
- **Spotless**: Enforces consistent code formatting via Google Java Format.
|
||||||
|
|
||||||
|
## Trusted Sources
|
||||||
|
- [Java SE 21 Documentation](https://docs.oracle.com/en/java/javase/21/docs/api/)
|
||||||
|
- [Maven Official Documentation](https://maven.apache.org/guides/index.html)
|
||||||
|
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
|
||||||
|
- [Project Wiki](https://github.com/iluwatar/java-design-patterns/wiki)
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
- `/pom.xml`: The root Maven configuration file that defines global dependencies and lists all pattern modules.
|
||||||
|
- `/[pattern-name]/`: Individual folders for each design pattern (e.g., `/abstract-factory`, `/builder`), acting as standalone Maven modules.
|
||||||
|
- `/[pattern-name]/src/main/java/`: Contains the actual Java implementation classes of the specific design pattern.
|
||||||
|
- `/[pattern-name]/src/test/java/`: Contains the JUnit tests verifying the pattern's behavior.
|
||||||
|
- `/[pattern-name]/README.md`: The documentation for the pattern, which gets published to the main website.
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Keep pattern implementations simple, atomic, and easy to understand.
|
||||||
|
- Write descriptive and meaningful names for classes, interfaces, and methods.
|
||||||
|
- Always include comprehensive unit tests for every new pattern or code modification.
|
||||||
|
- Follow the Google Java Format strictly (enforced by Spotless).
|
||||||
|
- Document the intent, explanation, and real-world usage clearly in each module's `README.md`.
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Avoid overcomplicating patterns with unnecessary external dependencies or complex frameworks.
|
||||||
|
- Do not introduce business logic that distracts from the core mechanism of the design pattern itself.
|
||||||
|
- Submitting new patterns or features without corresponding unit tests is strictly discouraged.
|
||||||
|
- Avoid large monolithic packages; each pattern should reside in its own isolated module.
|
||||||
|
|
||||||
|
## Example Design Pattern
|
||||||
|
When a new design pattern is added to the repository, it generally follows these steps:
|
||||||
|
- **Create a Module**: Create a new folder for the pattern in the root directory (e.g., `/my-new-pattern`).
|
||||||
|
- **Update root pom.xml**: Add `<module>my-new-pattern</module>` to the `<modules>` section of the root `pom.xml`.
|
||||||
|
- **Add Module pom.xml**: Create a `pom.xml` inside the new folder that inherits from the parent project.
|
||||||
|
- **Write the Code**: Implement the pattern logic under `src/main/java/com/iluwatar/mynewpattern`, usually including an `App.java` class to demonstrate its usage.
|
||||||
|
- **Write the Tests**: Add comprehensive unit tests under `src/test/java/com/iluwatar/mynewpattern`.
|
||||||
|
- **Document**: Create a `README.md` at the root of the new module, structured with standard sections like Intent, Explanation, Class diagram, Applicability, and Real world examples.
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
# Design Patterns Implemented in Java
|
# Design Patterns Implemented in Java
|
||||||
|
|
||||||

|
[](https://github.com/iluwatar/java-design-patterns/actions/workflows/maven-ci.yml)
|
||||||
[](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
|
[](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
|
||||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
Read in different language : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md), [**vi**](localization/vi/README.md), [**bn**](localization/bn/README.md), [**np**](localization/ne/README.md), [**it**](localization/it/README.md), [**da**](localization/da/README.md)
|
Read in different language : [**zh**](localization/zh/README.md), [**zh-TW**](localization/zh-TW/README.md)
|
||||||
|
,[**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md), [**vi**](localization/vi/README.md), [**bn**](localization/bn/README.md), [**np**](localization/ne/README.md), [**it**](localization/it/README.md), [**da**](localization/da/README.md)
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
@@ -53,6 +54,26 @@ The project contributors can get the book for free. Contact the maintainer via [
|
|||||||
|
|
||||||
This project is licensed under the terms of the MIT license.
|
This project is licensed under the terms of the MIT license.
|
||||||
|
|
||||||
|
# Supporters
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="center" width="32%">
|
||||||
|
<a href="https://serpapi.com/?utm_source=javadesignpatterns">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="assets/banner-wide.svg">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="assets/banner-wide-black.svg">
|
||||||
|
<img src="assets/banner-wide-black.svg" width="280" alt="SerpApi logo">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td valign="middle">
|
||||||
|
<h3><a href="https://serpapi.com/?utm_source=javadesignpatterns">SerpApi</a></h3>
|
||||||
|
<p>SerpApi is the world’s leading provider of search data, pioneering APIs that turn search engine results into structured, developer-ready information.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
@@ -481,7 +502,7 @@ This project is licensed under the terms of the MIT license.
|
|||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/takikuba"><img src="https://avatars.githubusercontent.com/u/58841343?v=4?s=100" width="100px;" alt="Jakub Klimek"/><br /><sub><b>Jakub Klimek</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=takikuba" title="Code">💻</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/takikuba"><img src="https://avatars.githubusercontent.com/u/58841343?v=4?s=100" width="100px;" alt="Jakub Klimek"/><br /><sub><b>Jakub Klimek</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=takikuba" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/bachbil"><img src="https://avatars.githubusercontent.com/u/28935461?v=4?s=100" width="100px;" alt="believe"/><br /><sub><b>believe</b></sub></a><br /><a href="#translation-bachbil" title="Translation">🌍</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/bachbil"><img src="https://avatars.githubusercontent.com/u/28935461?v=4?s=100" width="100px;" alt="believe"/><br /><sub><b>believe</b></sub></a><br /><a href="#translation-bachbil" title="Translation">🌍</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/egg0102030405"><img src="https://avatars.githubusercontent.com/u/59650695?v=4?s=100" width="100px;" alt="egg0102030405"/><br /><sub><b>egg0102030405</b></sub></a><br /><a href="#translation-egg0102030405" title="Translation">🌍</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=egg0102030405" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/egg0102030405"><img src="https://avatars.githubusercontent.com/u/59650695?v=4?s=100" width="100px;" alt="egg0102030405"/><br /><sub><b>egg0102030405</b></sub></a><br /><a href="#translation-egg0102030405" title="Translation">🌍</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=egg0102030405" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="http://vedasole.cloud"><img src="https://avatars.githubusercontent.com/u/77259804?v=4?s=100" width="100px;" alt="Ved Asole"/><br /><sub><b>Ved Asole</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ved-asole" title="Code">💻</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/ved-asole"><img src="https://avatars.githubusercontent.com/u/77259804?v=4?s=100" width="100px;" alt="Ved Asole"/><br /><sub><b>Ved Asole</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ved-asole" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/Holiday-C"><img src="https://avatars.githubusercontent.com/u/95869761?v=4?s=100" width="100px;" alt="NewMorning"/><br /><sub><b>NewMorning</b></sub></a><br /><a href="#translation-Holiday-C" title="Translation">🌍</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/Holiday-C"><img src="https://avatars.githubusercontent.com/u/95869761?v=4?s=100" width="100px;" alt="NewMorning"/><br /><sub><b>NewMorning</b></sub></a><br /><a href="#translation-Holiday-C" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -576,6 +597,47 @@ This project is licensed under the terms of the MIT license.
|
|||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/e5LA"><img src="https://avatars.githubusercontent.com/u/208197507?v=4?s=100" width="100px;" alt="e5LA"/><br /><sub><b>e5LA</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=e5LA" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=e5LA" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/e5LA"><img src="https://avatars.githubusercontent.com/u/208197507?v=4?s=100" width="100px;" alt="e5LA"/><br /><sub><b>e5LA</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=e5LA" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=e5LA" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="http://maziyar-gerami.github.io/portfolio/"><img src="https://avatars.githubusercontent.com/u/122622721?v=4?s=100" width="100px;" alt="Maziyar Gerami"/><br /><sub><b>Maziyar Gerami</b></sub></a><br /><a href="#translation-maziyar-gerami" title="Translation">🌍</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="http://maziyar-gerami.github.io/portfolio/"><img src="https://avatars.githubusercontent.com/u/122622721?v=4?s=100" width="100px;" alt="Maziyar Gerami"/><br /><sub><b>Maziyar Gerami</b></sub></a><br /><a href="#translation-maziyar-gerami" title="Translation">🌍</a></td>
|
||||||
<td align="center" valign="top" width="16.66%"><a href="https://github.com/yybmion"><img src="https://avatars.githubusercontent.com/u/113106136?v=4?s=100" width="100px;" alt="yoobin_mion"/><br /><sub><b>yoobin_mion</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yybmion" title="Code">💻</a></td>
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/yybmion"><img src="https://avatars.githubusercontent.com/u/113106136?v=4?s=100" width="100px;" alt="yoobin_mion"/><br /><sub><b>yoobin_mion</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yybmion" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://ronodhirsoumik.github.io"><img src="https://avatars.githubusercontent.com/u/46843689?v=4?s=100" width="100px;" alt="Soumik Sarker"/><br /><sub><b>Soumik Sarker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ronodhirSoumik" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/naman-sriv"><img src="https://avatars.githubusercontent.com/u/82610773?v=4?s=100" width="100px;" alt="Naman Srivastava"/><br /><sub><b>Naman Srivastava</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=naman-sriv" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/letdtcode"><img src="https://avatars.githubusercontent.com/u/92111552?v=4?s=100" width="100px;" alt="Thanh Nguyen Duc"/><br /><sub><b>Thanh Nguyen Duc</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=letdtcode" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/skamble2"><img src="https://avatars.githubusercontent.com/u/121136639?v=4?s=100" width="100px;" alt="Soham Kamble"/><br /><sub><b>Soham Kamble</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=skamble2" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/Olexandr88"><img src="https://avatars.githubusercontent.com/u/93856062?v=4?s=100" width="100px;" alt="Olexandr88"/><br /><sub><b>Olexandr88</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Olexandr88" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/nilsreichardt"><img src="https://avatars.githubusercontent.com/u/24459435?v=4?s=100" width="100px;" alt="Nils Reichardt"/><br /><sub><b>Nils Reichardt</b></sub></a><br /><a href="#security-nilsreichardt" title="Security">🛡️</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="http://www.northecho.dev"><img src="https://avatars.githubusercontent.com/u/122107484?v=4?s=100" width="100px;" alt="Christopher Lusk"/><br /><sub><b>Christopher Lusk</b></sub></a><br /><a href="#security-north-echo" title="Security">🛡️</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/dagecko"><img src="https://avatars.githubusercontent.com/u/6371704?v=4?s=100" width="100px;" alt="dagecko"/><br /><sub><b>dagecko</b></sub></a><br /><a href="#security-dagecko" title="Security">🛡️</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/jackmisbach"><img src="https://avatars.githubusercontent.com/u/183796460?v=4?s=100" width="100px;" alt="jackmisbach"/><br /><sub><b>jackmisbach</b></sub></a><br /><a href="#security-jackmisbach" title="Security">🛡️</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/avivdon"><img src="https://avatars.githubusercontent.com/u/74790003?v=4?s=100" width="100px;" alt="avivdon"/><br /><sub><b>avivdon</b></sub></a><br /><a href="#security-avivdon" title="Security">🛡️</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/quantdevv"><img src="https://avatars.githubusercontent.com/u/56085648?v=4?s=100" width="100px;" alt="Aditya Kumawat"/><br /><sub><b>Aditya Kumawat</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=quantdevv" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/devch37"><img src="https://avatars.githubusercontent.com/u/72903623?v=4?s=100" width="100px;" alt="devch37"/><br /><sub><b>devch37</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=devch37" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/HY-love-sleep"><img src="https://avatars.githubusercontent.com/u/73268470?v=4?s=100" width="100px;" alt="HY-love-sleep"/><br /><sub><b>HY-love-sleep</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=HY-love-sleep" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="http://www.cmd137blog.top/"><img src="https://avatars.githubusercontent.com/u/56242956?v=4?s=100" width="100px;" alt="CMD137"/><br /><sub><b>CMD137</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=CMD137" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/QuantumRange"><img src="https://avatars.githubusercontent.com/u/49843948?v=4?s=100" width="100px;" alt="QuantumRange"/><br /><sub><b>QuantumRange</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=QuantumRange" title="Documentation">📖</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/Dhiraj-birajdar"><img src="https://avatars.githubusercontent.com/u/114354331?v=4?s=100" width="100px;" alt="Dhiraj Birajdar"/><br /><sub><b>Dhiraj Birajdar</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Dhiraj-birajdar" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/sieberss"><img src="https://avatars.githubusercontent.com/u/178474619?v=4?s=100" width="100px;" alt="Stefan Siebers"/><br /><sub><b>Stefan Siebers</b></sub></a><br /><a href="#translation-sieberss" title="Translation">🌍</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://orbisappsec.com/"><img src="https://avatars.githubusercontent.com/u/242526317?v=4?s=100" width="100px;" alt="OrbisAI Security"/><br /><sub><b>OrbisAI Security</b></sub></a><br /><a href="#security-orbisai0security" title="Security">🛡️</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://www.rajugupta.com/portfolio/"><img src="https://avatars.githubusercontent.com/u/64011583?v=4?s=100" width="100px;" alt="Raju Gupta"/><br /><sub><b>Raju Gupta</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=rranjangupta" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/alex052525"><img src="https://avatars.githubusercontent.com/u/116088682?v=4?s=100" width="100px;" alt="김정모"/><br /><sub><b>김정모</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=alex052525" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/AnakinYen0329"><img src="https://avatars.githubusercontent.com/u/122139183?v=4?s=100" width="100px;" alt="Anakin Yen"/><br /><sub><b>Anakin Yen</b></sub></a><br /><a href="#translation-AnakinYen0329" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/SrGiuliano77"><img src="https://avatars.githubusercontent.com/u/128543464?v=4?s=100" width="100px;" alt="Giuliano Oliveira"/><br /><sub><b>Giuliano Oliveira</b></sub></a><br /><a href="#translation-SrGiuliano77" title="Translation">🌍</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://www.priyanshu-portfolio.co.in"><img src="https://avatars.githubusercontent.com/u/222148451?v=4?s=100" width="100px;" alt="Priyanshu"/><br /><sub><b>Priyanshu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=priyanshuvishwakarma273403" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/arnabnandy-cognizant"><img src="https://avatars.githubusercontent.com/u/5053648?v=4?s=100" width="100px;" alt="Arnab Nandy"/><br /><sub><b>Arnab Nandy</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=arnabnandy7" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/MohamedMBG"><img src="https://avatars.githubusercontent.com/u/123114744?v=4?s=100" width="100px;" alt="BAGHDAD Mohamed"/><br /><sub><b>BAGHDAD Mohamed</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=MohamedMBG" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/AnveshSrivastava"><img src="https://avatars.githubusercontent.com/u/170334970?v=4?s=100" width="100px;" alt="Anvesh Srivastava"/><br /><sub><b>Anvesh Srivastava</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=AnveshSrivastava" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/Mukul-Howale"><img src="https://avatars.githubusercontent.com/u/110479646?v=4?s=100" width="100px;" alt="Mukul Howale"/><br /><sub><b>Mukul Howale</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Mukul-Howale" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/SandhyaDevadiga"><img src="https://avatars.githubusercontent.com/u/128058717?v=4?s=100" width="100px;" alt="Sandhya"/><br /><sub><b>Sandhya</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=SandhyaDevadiga" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/devikae"><img src="https://avatars.githubusercontent.com/u/98100047?v=4?s=100" width="100px;" alt="dev-ikae"/><br /><sub><b>dev-ikae</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=devikae" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/zanarellidev"><img src="https://avatars.githubusercontent.com/u/268068569?v=4?s=100" width="100px;" alt="zanarelli"/><br /><sub><b>zanarelli</b></sub></a><br /><a href="#translation-zanarellidev" title="Translation">🌍</a></td>
|
||||||
|
<td align="center" valign="top" width="16.66%"><a href="http://blog.kaizensphere.dev"><img src="https://avatars.githubusercontent.com/u/206610174?v=4?s=100" width="100px;" alt="KaizenCode"/><br /><sub><b>KaizenCode</b></sub></a><br /><a href="#translation-kaizencode-art" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="1920mm"
|
||||||
|
height="1080mm"
|
||||||
|
viewBox="0 0 1920 1080"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline">
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.467025"
|
||||||
|
id="rect1"
|
||||||
|
width="1920"
|
||||||
|
height="1080"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="layer2"
|
||||||
|
transform="translate(-0.07725886,24.282698)">
|
||||||
|
<path
|
||||||
|
d="m 1603.93,417.60042 v 24.10577 h 43.6314 V 393.4945 H 1603.93 Z M 726.59342,401.51523 c -0.56895,0.0438 -2.57076,0.17532 -4.42628,0.30681 -14.37442,1.00806 -26.09338,3.96648 -33.93427,8.59047 -12.20323,7.18791 -18.2733,18.84636 -19.91785,38.30689 -0.56896,6.90234 -0.4635,21.54162 0.23163,29.58421 2.08727,24.41266 10.05441,34.42779 33.70252,42.40418 5.05916,1.68766 11.6135,3.4631 20.69781,5.58885 4.21609,0.9855 9.92734,2.3883 12.70985,3.11125 15.13357,3.94508 20.02334,7.12254 21.60399,14.00333 1.09635,4.79969 1.09635,16.28306 0.0204,21.45462 -1.89632,8.96265 -7.60835,12.22789 -22.29923,12.66674 -19.30654,0.61363 -37.30651,-2.03798 -58.93207,-8.65622 -2.19197,-0.65751 -4.0467,-1.11793 -4.15219,-1.03016 -0.18943,0.24098 -7.65069,36.24635 -7.52443,36.378 0.27412,0.28489 7.25035,3.13358 10.47555,4.27307 12.62517,4.49249 25.96712,7.29808 41.62733,8.76632 4.0467,0.37264 19.41202,0.50352 24.66056,0.21865 28.15909,-1.57757 44.26199,-7.97638 53.34629,-21.21286 7.22956,-10.51944 9.86422,-25.6838 8.95725,-51.5647 -0.48427,-13.76234 -1.58062,-20.18348 -4.55254,-26.62618 -5.79595,-12.53508 -18.75833,-19.74539 -49.63679,-27.63401 -19.55909,-4.99678 -22.10982,-5.71974 -25.98792,-7.31963 -5.41717,-2.25741 -7.90401,-4.82125 -8.9996,-9.31374 -0.77995,-3.17746 -1.09638,-12.25021 -0.61132,-17.31244 0.84307,-8.744 4.23609,-12.33799 13.0463,-13.80623 7.1033,-1.18337 24.15473,-0.78917 38.88795,0.8985 5.6905,0.65751 15.70254,2.12575 23.37404,3.44078 3.43614,0.5913 5.48029,0.85461 5.52263,0.70139 0.12629,-0.41652 4.53176,-39.40247 4.44708,-39.46823 -0.14782,-0.17529 -8.53613,-1.86275 -13.40509,-2.69549 -10.20146,-1.77505 -19.11716,-2.87081 -30.89927,-3.7693 -2.97189,-0.21912 -25.73537,-0.46018 -28.03282,-0.28489 z m 568.87548,3.19956 c -0.2157,0.63549 -61.4398,209.67776 -61.4398,209.78709 0,0.0662 10.6403,0.0878 23.6443,0.0662 l 23.6751,-0.0662 5.3047,-19.94173 c 2.9104,-10.97907 5.3587,-20.16192 5.4434,-20.44679 l 0.1001,-0.48197 33.8919,0.0439 33.8766,0.0662 5.374,20.16116 c 2.9719,11.0884 5.4357,20.29281 5.4819,20.44602 0.085,0.21944 4.9275,0.26332 23.7289,0.21944 l 23.6058,-0.0662 -30.8969,-104.97027 -30.9201,-104.97027 -30.3964,-0.0658 c -24.191,-0.0438 -30.4119,0 -30.4735,0.21921 z m 42.0069,85.4664 c 6.2825,24.01845 11.4256,43.74152 11.4256,43.82929 0,0.10933 -10.4402,0.17477 -23.2131,0.17477 -19.2404,0 -23.1824,-0.0439 -23.0977,-0.28488 0.062,-0.17477 5.3125,-19.89784 11.6566,-43.82853 6.3442,-23.95301 11.6105,-43.54442 11.6797,-43.54442 0.062,0 5.2663,19.65762 11.5489,43.65375 z M 887.09657,457.83526 c -3.66713,0.19709 -8.13578,0.59207 -10.22226,0.87694 -27.35836,3.83498 -43.37734,18.82457 -47.73971,44.72703 -1.09638,6.50891 -1.11716,6.77145 -1.20184,31.29421 -0.10534,24.12778 -0.0204,27.76566 0.8007,33.83572 1.49673,11.13306 5.26935,20.97265 10.83434,28.18219 7.86166,10.21223 19.981,16.85203 35.59884,19.48208 7.1872,1.22726 12.09854,1.53368 21.52007,1.38047 11.25547,-0.19709 20.76092,-1.2927 32.35362,-3.76953 8.26201,-1.75311 20.44447,-5.39099 20.44447,-6.07006 0,-0.26332 -6.09085,-33.00344 -6.23868,-33.50696 -0.0838,-0.32876 -0.18943,-0.32876 -1.09634,-0.0662 -8.59925,2.71782 -23.29013,5.23777 -35.17769,6.02694 -5.227,0.35031 -16.31387,0.15321 -19.20108,-0.32876 -8.64158,-1.46824 -12.75143,-4.47093 -14.81711,-10.8913 -0.86463,-2.71782 -1.24345,-5.52264 -1.39127,-10.78197 l -0.12629,-4.53638 h 79.73456 v -16.17296 c 0,-16.78659 -0.18943,-23.97457 -0.75835,-29.25623 -2.57155,-24.25945 -12.75223,-39.18282 -31.53136,-46.19527 -4.74271,-1.75311 -11.29784,-3.19979 -17.45256,-3.79109 -3.26678,-0.32875 -11.73977,-0.59207 -14.33209,-0.43885 z m 8.21968,35.80826 c 4.5741,0.59207 7.50365,1.88477 9.92737,4.38316 3.43614,3.57167 4.46861,7.29731 4.49018,16.32618 l 0.0204,5.25933 h -38.14958 l -0.0632,-3.28679 c -0.16848,-9.02887 1.32734,-14.17887 5.18464,-17.90452 2.38213,-2.27897 5.33249,-3.7472 8.9157,-4.42628 3.07738,-0.59207 6.74451,-0.72372 9.67406,-0.35108 z m 273.26965,-35.69816 c -8.2844,0.5913 -17.9161,3.19902 -27.5478,7.45053 -3.7572,1.66534 -9.3623,4.60182 -12.2495,6.39881 l -2.2789,1.42435 -0.2541,-0.67907 c -0.1463,-0.3942 -1.178,-3.13357 -2.2712,-6.11394 l -1.9787,-5.43487 h -34.2538 v 110.99644 c 0,101.83669 0.023,110.9972 0.3387,110.9972 0.1848,0 9.9012,-1.38045 21.5809,-3.06812 11.6951,-1.68768 21.3499,-3.06815 21.4808,-3.06815 0.1694,0 0.2311,-9.29141 0.2311,-31.7323 V 613.3624 l 1.7477,0.39497 c 7.5067,1.73078 17.2847,3.06736 27.0858,3.72487 5.0969,0.32876 15.2984,0.154 18.6937,-0.32875 14.3129,-2.08187 24.2987,-7.99871 30.9585,-18.32027 1.6092,-2.47607 4.1114,-7.82317 5.1431,-10.95752 1.6015,-4.86436 2.6408,-10.10213 3.3569,-16.76426 0.2694,-2.54228 0.3387,-7.6484 0.4157,-29.80365 0.1077,-27.15203 0,-33.17897 -0.6698,-38.89795 -2.1096,-18.01384 -8.7694,-30.61514 -19.9641,-37.82468 -6.0669,-3.92275 -13.2349,-6.07006 -22.1891,-6.66213 -3.48,-0.24097 -4.0036,-0.24097 -7.3758,0.0224 z m -7.0679,39.42381 c 4.196,0.41653 6.8292,1.49057 9.0851,3.68176 2.2558,2.1912 3.5031,4.82125 4.0882,8.65622 0.4697,3.00193 0.Line truncated
|
||||||
|
fill="url(#paint0_linear_250_69)"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#313131;fill-opacity:1;stroke-width:0.76992" />
|
||||||
|
<path
|
||||||
|
d="m 338.17024,593.15362 c 4.17621,14.81175 16.59342,26.40907 32.28441,30.05926 v 61.19948 h -0.8446 c -51.68157,0 -93.93893,-40.34539 -96.99586,-91.25874 z m 32.28441,-185.53765 c -19.40124,4.51297 -33.79938,21.17799 -33.79938,41.04503 1.5e-4,23.33323 19.8615,42.24942 44.36116,42.24942 24.50043,0 44.36208,-18.91619 44.36208,-42.24942 0,-19.86781 -14.39829,-36.53229 -33.79953,-41.04503 V 346.41702 H 513.2573 c 51.96737,0 94.40695,40.79288 97.04394,92.10412 h -66.22092 c -4.76812,-18.43653 -22.24456,-32.10956 -43.07477,-32.10964 -24.50043,0 -44.36208,18.9155 -44.36208,42.2495 0,19.86704 14.39906,36.53121 33.79953,41.04372 v 51.41918 c -19.40124,4.51251 -33.79953,21.17745 -33.79953,41.04449 0,19.86705 14.39906,36.53122 33.79953,41.04449 v 61.19948 h -98.86402 v -61.19948 c 19.40124,-4.51327 33.79953,-21.17744 33.79953,-41.04449 0,-23.33323 -19.86165,-42.24942 -44.36208,-42.24942 -20.82945,0 -38.30596,13.6738 -43.0747,32.10956 H 272.436 V 443.59107 c 0,-53.66758 43.50647,-97.17405 97.17405,-97.17405 h 0.8446 z m 239.97669,179.62235 c 0,53.66735 -43.50668,97.17404 -97.17404,97.17404 h -1.68921 v -61.19948 c 19.40047,-4.51327 33.79877,-21.17744 33.79877,-41.04449 0,-19.86704 -14.3983,-36.53198 -33.79877,-41.04449 v -51.41918 c 15.69022,-3.64943 28.10828,-15.24674 32.28433,-30.05926 h 66.57892 z"
|
||||||
|
fill="url(#paint1_linear_250_69)"
|
||||||
|
id="path2"
|
||||||
|
style="fill:#313131;fill-opacity:1;stroke-width:0.76992" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="1920mm"
|
||||||
|
height="1080mm"
|
||||||
|
viewBox="0 0 1920 1080"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline">
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:#313131;fill-opacity:1;stroke-width:0.467025"
|
||||||
|
id="rect1"
|
||||||
|
width="1920"
|
||||||
|
height="1080"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="layer2"
|
||||||
|
transform="translate(-0.07725886,24.282698)">
|
||||||
|
<path
|
||||||
|
d="m 1603.93,417.60042 v 24.10577 h 43.6314 V 393.4945 H 1603.93 Z M 726.59342,401.51523 c -0.56895,0.0438 -2.57076,0.17532 -4.42628,0.30681 -14.37442,1.00806 -26.09338,3.96648 -33.93427,8.59047 -12.20323,7.18791 -18.2733,18.84636 -19.91785,38.30689 -0.56896,6.90234 -0.4635,21.54162 0.23163,29.58421 2.08727,24.41266 10.05441,34.42779 33.70252,42.40418 5.05916,1.68766 11.6135,3.4631 20.69781,5.58885 4.21609,0.9855 9.92734,2.3883 12.70985,3.11125 15.13357,3.94508 20.02334,7.12254 21.60399,14.00333 1.09635,4.79969 1.09635,16.28306 0.0204,21.45462 -1.89632,8.96265 -7.60835,12.22789 -22.29923,12.66674 -19.30654,0.61363 -37.30651,-2.03798 -58.93207,-8.65622 -2.19197,-0.65751 -4.0467,-1.11793 -4.15219,-1.03016 -0.18943,0.24098 -7.65069,36.24635 -7.52443,36.378 0.27412,0.28489 7.25035,3.13358 10.47555,4.27307 12.62517,4.49249 25.96712,7.29808 41.62733,8.76632 4.0467,0.37264 19.41202,0.50352 24.66056,0.21865 28.15909,-1.57757 44.26199,-7.97638 53.34629,-21.21286 7.22956,-10.51944 9.86422,-25.6838 8.95725,-51.5647 -0.48427,-13.76234 -1.58062,-20.18348 -4.55254,-26.62618 -5.79595,-12.53508 -18.75833,-19.74539 -49.63679,-27.63401 -19.55909,-4.99678 -22.10982,-5.71974 -25.98792,-7.31963 -5.41717,-2.25741 -7.90401,-4.82125 -8.9996,-9.31374 -0.77995,-3.17746 -1.09638,-12.25021 -0.61132,-17.31244 0.84307,-8.744 4.23609,-12.33799 13.0463,-13.80623 7.1033,-1.18337 24.15473,-0.78917 38.88795,0.8985 5.6905,0.65751 15.70254,2.12575 23.37404,3.44078 3.43614,0.5913 5.48029,0.85461 5.52263,0.70139 0.12629,-0.41652 4.53176,-39.40247 4.44708,-39.46823 -0.14782,-0.17529 -8.53613,-1.86275 -13.40509,-2.69549 -10.20146,-1.77505 -19.11716,-2.87081 -30.89927,-3.7693 -2.97189,-0.21912 -25.73537,-0.46018 -28.03282,-0.28489 z m 568.87548,3.19956 c -0.2157,0.63549 -61.4398,209.67776 -61.4398,209.78709 0,0.0662 10.6403,0.0878 23.6443,0.0662 l 23.6751,-0.0662 5.3047,-19.94173 c 2.9104,-10.97907 5.3587,-20.16192 5.4434,-20.44679 l 0.1001,-0.48197 33.8919,0.0439 33.8766,0.0662 5.374,20.16116 c 2.9719,11.0884 5.4357,20.29281 5.4819,20.44602 0.085,0.21944 4.9275,0.26332 23.7289,0.21944 l 23.6058,-0.0662 -30.8969,-104.97027 -30.9201,-104.97027 -30.3964,-0.0658 c -24.191,-0.0438 -30.4119,0 -30.4735,0.21921 z m 42.0069,85.4664 c 6.2825,24.01845 11.4256,43.74152 11.4256,43.82929 0,0.10933 -10.4402,0.17477 -23.2131,0.17477 -19.2404,0 -23.1824,-0.0439 -23.0977,-0.28488 0.062,-0.17477 5.3125,-19.89784 11.6566,-43.82853 6.3442,-23.95301 11.6105,-43.54442 11.6797,-43.54442 0.062,0 5.2663,19.65762 11.5489,43.65375 z M 887.09657,457.83526 c -3.66713,0.19709 -8.13578,0.59207 -10.22226,0.87694 -27.35836,3.83498 -43.37734,18.82457 -47.73971,44.72703 -1.09638,6.50891 -1.11716,6.77145 -1.20184,31.29421 -0.10534,24.12778 -0.0204,27.76566 0.8007,33.83572 1.49673,11.13306 5.26935,20.97265 10.83434,28.18219 7.86166,10.21223 19.981,16.85203 35.59884,19.48208 7.1872,1.22726 12.09854,1.53368 21.52007,1.38047 11.25547,-0.19709 20.76092,-1.2927 32.35362,-3.76953 8.26201,-1.75311 20.44447,-5.39099 20.44447,-6.07006 0,-0.26332 -6.09085,-33.00344 -6.23868,-33.50696 -0.0838,-0.32876 -0.18943,-0.32876 -1.09634,-0.0662 -8.59925,2.71782 -23.29013,5.23777 -35.17769,6.02694 -5.227,0.35031 -16.31387,0.15321 -19.20108,-0.32876 -8.64158,-1.46824 -12.75143,-4.47093 -14.81711,-10.8913 -0.86463,-2.71782 -1.24345,-5.52264 -1.39127,-10.78197 l -0.12629,-4.53638 h 79.73456 v -16.17296 c 0,-16.78659 -0.18943,-23.97457 -0.75835,-29.25623 -2.57155,-24.25945 -12.75223,-39.18282 -31.53136,-46.19527 -4.74271,-1.75311 -11.29784,-3.19979 -17.45256,-3.79109 -3.26678,-0.32875 -11.73977,-0.59207 -14.33209,-0.43885 z m 8.21968,35.80826 c 4.5741,0.59207 7.50365,1.88477 9.92737,4.38316 3.43614,3.57167 4.46861,7.29731 4.49018,16.32618 l 0.0204,5.25933 h -38.14958 l -0.0632,-3.28679 c -0.16848,-9.02887 1.32734,-14.17887 5.18464,-17.90452 2.38213,-2.27897 5.33249,-3.7472 8.9157,-4.42628 3.07738,-0.59207 6.74451,-0.72372 9.67406,-0.35108 z m 273.26965,-35.69816 c -8.2844,0.5913 -17.9161,3.19902 -27.5478,7.45053 -3.7572,1.66534 -9.3623,4.60182 -12.2495,6.39881 l -2.2789,1.42435 -0.2541,-0.67907 c -0.1463,-0.3942 -1.178,-3.13357 -2.2712,-6.11394 l -1.9787,-5.43487 h -34.2538 v 110.99644 c 0,101.83669 0.023,110.9972 0.3387,110.9972 0.1848,0 9.9012,-1.38045 21.5809,-3.06812 11.6951,-1.68768 21.3499,-3.06815 21.4808,-3.06815 0.1694,0 0.2311,-9.29141 0.2311,-31.7323 V 613.3624 l 1.7477,0.39497 c 7.5067,1.73078 17.2847,3.06736 27.0858,3.72487 5.0969,0.32876 15.2984,0.154 18.6937,-0.32875 14.3129,-2.08187 24.2987,-7.99871 30.9585,-18.32027 1.6092,-2.47607 4.1114,-7.82317 5.1431,-10.95752 1.6015,-4.86436 2.6408,-10.10213 3.3569,-16.76426 0.2694,-2.54228 0.3387,-7.6484 0.4157,-29.80365 0.1077,-27.15203 0,-33.17897 -0.6698,-38.89795 -2.1096,-18.01384 -8.7694,-30.61514 -19.9641,-37.82468 -6.0669,-3.92275 -13.2349,-6.07006 -22.1891,-6.66213 -3.48,-0.24097 -4.0036,-0.24097 -7.3758,0.0224 z m -7.0679,39.42381 c 4.196,0.41653 6.8292,1.49057 9.0851,3.68176 2.2558,2.1912 3.5031,4.82125 4.0882,8.65622 0.4697,3.00193 0.Line truncated
|
||||||
|
fill="url(#paint0_linear_250_69)"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.76992" />
|
||||||
|
<path
|
||||||
|
d="m 338.17024,593.15362 c 4.17621,14.81175 16.59342,26.40907 32.28441,30.05926 v 61.19948 h -0.8446 c -51.68157,0 -93.93893,-40.34539 -96.99586,-91.25874 z m 32.28441,-185.53765 c -19.40124,4.51297 -33.79938,21.17799 -33.79938,41.04503 1.5e-4,23.33323 19.8615,42.24942 44.36116,42.24942 24.50043,0 44.36208,-18.91619 44.36208,-42.24942 0,-19.86781 -14.39829,-36.53229 -33.79953,-41.04503 V 346.41702 H 513.2573 c 51.96737,0 94.40695,40.79288 97.04394,92.10412 h -66.22092 c -4.76812,-18.43653 -22.24456,-32.10956 -43.07477,-32.10964 -24.50043,0 -44.36208,18.9155 -44.36208,42.2495 0,19.86704 14.39906,36.53121 33.79953,41.04372 v 51.41918 c -19.40124,4.51251 -33.79953,21.17745 -33.79953,41.04449 0,19.86705 14.39906,36.53122 33.79953,41.04449 v 61.19948 h -98.86402 v -61.19948 c 19.40124,-4.51327 33.79953,-21.17744 33.79953,-41.04449 0,-23.33323 -19.86165,-42.24942 -44.36208,-42.24942 -20.82945,0 -38.30596,13.6738 -43.0747,32.10956 H 272.436 V 443.59107 c 0,-53.66758 43.50647,-97.17405 97.17405,-97.17405 h 0.8446 z m 239.97669,179.62235 c 0,53.66735 -43.50668,97.17404 -97.17404,97.17404 h -1.68921 v -61.19948 c 19.40047,-4.51327 33.79877,-21.17744 33.79877,-41.04449 0,-19.86704 -14.3983,-36.53198 -33.79877,-41.04449 v -51.41918 c 15.69022,-3.64943 28.10828,-15.24674 32.28433,-30.05926 h 66.57892 z"
|
||||||
|
fill="url(#paint1_linear_250_69)"
|
||||||
|
id="path2"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.76992" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="1920mm"
|
||||||
|
height="1080mm"
|
||||||
|
viewBox="0 0 1920 1080"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient1">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#377fea;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop1" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#6937ea;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop3" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
xlink:href="#linearGradient1"
|
||||||
|
id="linearGradient2"
|
||||||
|
x1="281.17816"
|
||||||
|
y1="1218.8219"
|
||||||
|
x2="1638.823"
|
||||||
|
y2="-138.82297"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
<linearGradient
|
||||||
|
id="paint1_linear_250_69"
|
||||||
|
x1="44.448799"
|
||||||
|
y1="434.60999"
|
||||||
|
x2="395.10001"
|
||||||
|
y2="38.412498"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.76992106,0,0,0.76992106,255.9798,345.35868)">
|
||||||
|
<stop
|
||||||
|
stop-color="#377FEA"
|
||||||
|
id="stop4" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-color="#8C45EF"
|
||||||
|
id="stop5" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline">
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:url(#linearGradient2);stroke-width:0.467025"
|
||||||
|
id="rect1"
|
||||||
|
width="1920"
|
||||||
|
height="1080"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="layer2"
|
||||||
|
transform="translate(15.268578,23.753531)">
|
||||||
|
<path
|
||||||
|
d="m 1603.93,417.60042 v 24.10577 h 43.6314 V 393.4945 H 1603.93 Z M 726.59342,401.51523 c -0.56895,0.0438 -2.57076,0.17532 -4.42628,0.30681 -14.37442,1.00806 -26.09338,3.96648 -33.93427,8.59047 -12.20323,7.18791 -18.2733,18.84636 -19.91785,38.30689 -0.56896,6.90234 -0.4635,21.54162 0.23163,29.58421 2.08727,24.41266 10.05441,34.42779 33.70252,42.40418 5.05916,1.68766 11.6135,3.4631 20.69781,5.58885 4.21609,0.9855 9.92734,2.3883 12.70985,3.11125 15.13357,3.94508 20.02334,7.12254 21.60399,14.00333 1.09635,4.79969 1.09635,16.28306 0.0204,21.45462 -1.89632,8.96265 -7.60835,12.22789 -22.29923,12.66674 -19.30654,0.61363 -37.30651,-2.03798 -58.93207,-8.65622 -2.19197,-0.65751 -4.0467,-1.11793 -4.15219,-1.03016 -0.18943,0.24098 -7.65069,36.24635 -7.52443,36.378 0.27412,0.28489 7.25035,3.13358 10.47555,4.27307 12.62517,4.49249 25.96712,7.29808 41.62733,8.76632 4.0467,0.37264 19.41202,0.50352 24.66056,0.21865 28.15909,-1.57757 44.26199,-7.97638 53.34629,-21.21286 7.22956,-10.51944 9.86422,-25.6838 8.95725,-51.5647 -0.48427,-13.76234 -1.58062,-20.18348 -4.55254,-26.62618 -5.79595,-12.53508 -18.75833,-19.74539 -49.63679,-27.63401 -19.55909,-4.99678 -22.10982,-5.71974 -25.98792,-7.31963 -5.41717,-2.25741 -7.90401,-4.82125 -8.9996,-9.31374 -0.77995,-3.17746 -1.09638,-12.25021 -0.61132,-17.31244 0.84307,-8.744 4.23609,-12.33799 13.0463,-13.80623 7.1033,-1.18337 24.15473,-0.78917 38.88795,0.8985 5.6905,0.65751 15.70254,2.12575 23.37404,3.44078 3.43614,0.5913 5.48029,0.85461 5.52263,0.70139 0.12629,-0.41652 4.53176,-39.40247 4.44708,-39.46823 -0.14782,-0.17529 -8.53613,-1.86275 -13.40509,-2.69549 -10.20146,-1.77505 -19.11716,-2.87081 -30.89927,-3.7693 -2.97189,-0.21912 -25.73537,-0.46018 -28.03282,-0.28489 z m 568.87548,3.19956 c -0.2157,0.63549 -61.4398,209.67776 -61.4398,209.78709 0,0.0662 10.6403,0.0878 23.6443,0.0662 l 23.6751,-0.0662 5.3047,-19.94173 c 2.9104,-10.97907 5.3587,-20.16192 5.4434,-20.44679 l 0.1001,-0.48197 33.8919,0.0439 33.8766,0.0662 5.374,20.16116 c 2.9719,11.0884 5.4357,20.29281 5.4819,20.44602 0.085,0.21944 4.9275,0.26332 23.7289,0.21944 l 23.6058,-0.0662 -30.8969,-104.97027 -30.9201,-104.97027 -30.3964,-0.0658 c -24.191,-0.0438 -30.4119,0 -30.4735,0.21921 z m 42.0069,85.4664 c 6.2825,24.01845 11.4256,43.74152 11.4256,43.82929 0,0.10933 -10.4402,0.17477 -23.2131,0.17477 -19.2404,0 -23.1824,-0.0439 -23.0977,-0.28488 0.062,-0.17477 5.3125,-19.89784 11.6566,-43.82853 6.3442,-23.95301 11.6105,-43.54442 11.6797,-43.54442 0.062,0 5.2663,19.65762 11.5489,43.65375 z M 887.09657,457.83526 c -3.66713,0.19709 -8.13578,0.59207 -10.22226,0.87694 -27.35836,3.83498 -43.37734,18.82457 -47.73971,44.72703 -1.09638,6.50891 -1.11716,6.77145 -1.20184,31.29421 -0.10534,24.12778 -0.0204,27.76566 0.8007,33.83572 1.49673,11.13306 5.26935,20.97265 10.83434,28.18219 7.86166,10.21223 19.981,16.85203 35.59884,19.48208 7.1872,1.22726 12.09854,1.53368 21.52007,1.38047 11.25547,-0.19709 20.76092,-1.2927 32.35362,-3.76953 8.26201,-1.75311 20.44447,-5.39099 20.44447,-6.07006 0,-0.26332 -6.09085,-33.00344 -6.23868,-33.50696 -0.0838,-0.32876 -0.18943,-0.32876 -1.09634,-0.0662 -8.59925,2.71782 -23.29013,5.23777 -35.17769,6.02694 -5.227,0.35031 -16.31387,0.15321 -19.20108,-0.32876 -8.64158,-1.46824 -12.75143,-4.47093 -14.81711,-10.8913 -0.86463,-2.71782 -1.24345,-5.52264 -1.39127,-10.78197 l -0.12629,-4.53638 h 79.73456 v -16.17296 c 0,-16.78659 -0.18943,-23.97457 -0.75835,-29.25623 -2.57155,-24.25945 -12.75223,-39.18282 -31.53136,-46.19527 -4.74271,-1.75311 -11.29784,-3.19979 -17.45256,-3.79109 -3.26678,-0.32875 -11.73977,-0.59207 -14.33209,-0.43885 z m 8.21968,35.80826 c 4.5741,0.59207 7.50365,1.88477 9.92737,4.38316 3.43614,3.57167 4.46861,7.29731 4.49018,16.32618 l 0.0204,5.25933 h -38.14958 l -0.0632,-3.28679 c -0.16848,-9.02887 1.32734,-14.17887 5.18464,-17.90452 2.38213,-2.27897 5.33249,-3.7472 8.9157,-4.42628 3.07738,-0.59207 6.74451,-0.72372 9.67406,-0.35108 z m 273.26965,-35.69816 c -8.2844,0.5913 -17.9161,3.19902 -27.5478,7.45053 -3.7572,1.66534 -9.3623,4.60182 -12.2495,6.39881 l -2.2789,1.42435 -0.2541,-0.67907 c -0.1463,-0.3942 -1.178,-3.13357 -2.2712,-6.11394 l -1.9787,-5.43487 h -34.2538 v 110.99644 c 0,101.83669 0.023,110.9972 0.3387,110.9972 0.1848,0 9.9012,-1.38045 21.5809,-3.06812 11.6951,-1.68768 21.3499,-3.06815 21.4808,-3.06815 0.1694,0 0.2311,-9.29141 0.2311,-31.7323 V 613.3624 l 1.7477,0.39497 c 7.5067,1.73078 17.2847,3.06736 27.0858,3.72487 5.0969,0.32876 15.2984,0.154 18.6937,-0.32875 14.3129,-2.08187 24.2987,-7.99871 30.9585,-18.32027 1.6092,-2.47607 4.1114,-7.82317 5.1431,-10.95752 1.6015,-4.86436 2.6408,-10.10213 3.3569,-16.76426 0.2694,-2.54228 0.3387,-7.6484 0.4157,-29.80365 0.1077,-27.15203 0,-33.17897 -0.6698,-38.89795 -2.1096,-18.01384 -8.7694,-30.61514 -19.9641,-37.82468 -6.0669,-3.92275 -13.2349,-6.07006 -22.1891,-6.66213 -3.48,-0.24097 -4.0036,-0.24097 -7.3758,0.0224 z m -7.0679,39.42381 c 4.196,0.41653 6.8292,1.49057 9.0851,3.68176 2.2558,2.1912 3.5031,4.82125 4.0882,8.65622 0.4697,3.00193 0.Line truncated
|
||||||
|
fill="url(#paint0_linear_250_69)"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.76992" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.209372;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect7"
|
||||||
|
width="370"
|
||||||
|
height="370"
|
||||||
|
x="240.54073"
|
||||||
|
y="330.66769"
|
||||||
|
ry="123.22868"
|
||||||
|
rx="109.99104" />
|
||||||
|
<path
|
||||||
|
d="m 321.71404,592.09528 c 4.17621,14.81175 16.59342,26.40907 32.28441,30.05926 v 61.19948 h -0.8446 c -51.68157,0 -93.93893,-40.34539 -96.99586,-91.25874 z m 32.28441,-185.53765 c -19.40124,4.51297 -33.79938,21.17799 -33.79938,41.04503 1.5e-4,23.33323 19.8615,42.24942 44.36116,42.24942 24.50043,0 44.36208,-18.91619 44.36208,-42.24942 0,-19.86781 -14.39829,-36.53229 -33.79953,-41.04503 V 345.35868 H 496.8011 c 51.96737,0 94.40695,40.79288 97.04394,92.10412 h -66.22092 c -4.76812,-18.43653 -22.24456,-32.10956 -43.07477,-32.10964 -24.50043,0 -44.36208,18.9155 -44.36208,42.2495 0,19.86704 14.39906,36.53121 33.79953,41.04372 v 51.41918 c -19.40124,4.51251 -33.79953,21.17745 -33.79953,41.04449 0,19.86705 14.39906,36.53122 33.79953,41.04449 v 61.19948 h -98.86402 v -61.19948 c 19.40124,-4.51327 33.79953,-21.17744 33.79953,-41.04449 0,-23.33323 -19.86165,-42.24942 -44.36208,-42.24942 -20.82945,0 -38.30596,13.6738 -43.0747,32.10956 H 255.9798 V 442.53273 c 0,-53.66758 43.50647,-97.17405 97.17405,-97.17405 h 0.8446 z m 239.97669,179.62235 c 0,53.66735 -43.50668,97.17404 -97.17404,97.17404 h -1.68921 v -61.19948 c 19.40047,-4.51327 33.79877,-21.17744 33.79877,-41.04449 0,-19.86704 -14.3983,-36.53198 -33.79877,-41.04449 v -51.41918 c 15.69022,-3.64943 28.10828,-15.24674 32.28433,-30.05926 h 66.57892 z"
|
||||||
|
fill="url(#paint1_linear_250_69)"
|
||||||
|
id="path2"
|
||||||
|
style="fill:url(#paint1_linear_250_69);stroke-width:0.76992" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
title: "Backends For Frontends Pattern in Java: Tailoring APIs to Client Needs"
|
||||||
|
shortTitle: Backends For Frontends
|
||||||
|
description: "Learn the Backends For Frontends (BFF) design pattern in Java. Understand how to give each client type its own dedicated backend service, with real-world examples, code, and diagrams."
|
||||||
|
category: Architectural
|
||||||
|
language: en
|
||||||
|
tag:
|
||||||
|
- API design
|
||||||
|
- Architecture
|
||||||
|
- Client-server
|
||||||
|
- Decoupling
|
||||||
|
- Microservices
|
||||||
|
---
|
||||||
|
|
||||||
|
## Also known as
|
||||||
|
|
||||||
|
* Backend For Frontend
|
||||||
|
* BFF Pattern
|
||||||
|
|
||||||
|
## Intent of Backends For Frontends Pattern
|
||||||
|
|
||||||
|
Provide each client-side application (mobile, desktop, chatbot, and so on) with its own dedicated
|
||||||
|
backend service, so every client gets an API shaped exactly for its own needs instead of sharing
|
||||||
|
one general-purpose backend with every other client.
|
||||||
|
|
||||||
|
## Detailed Explanation of Backends For Frontends Pattern with Real-World Examples
|
||||||
|
|
||||||
|
Real-world example
|
||||||
|
|
||||||
|
> Imagine a retail company whose mobile app, desktop back-office tool, and support chatbot all
|
||||||
|
> need customer, cart, order and supplier data -- but a phone screen wants a short summary while
|
||||||
|
> the back-office desktop tool wants full order and stock detail. Rather than exposing one shared
|
||||||
|
> API that every client has to filter or over-fetch from, the company stands up a small BFF service
|
||||||
|
> for the mobile clients and a separate BFF service for the intranet clients. Each BFF calls only
|
||||||
|
> the downstream microservices its client needs and returns a payload shaped for that client.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Give every kind of client its own tailor-made backend, instead of forcing all clients through one
|
||||||
|
> one-size-fits-all API.
|
||||||
|
|
||||||
|
Sam Newman, who popularized the pattern, says
|
||||||
|
|
||||||
|
> Create separate backend services to be consumed by specific frontend applications or interfaces.
|
||||||
|
|
||||||
|
## Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
node mobile{
|
||||||
|
component iosapp as "ios app"
|
||||||
|
component androidapp as "android app"
|
||||||
|
}
|
||||||
|
node intranet{
|
||||||
|
component desktop as "desktop app"
|
||||||
|
component chatbot
|
||||||
|
}
|
||||||
|
component bff as "BFF server"{
|
||||||
|
component iosbff as "ios BFF"
|
||||||
|
component androidbff as "android BFF"
|
||||||
|
component chatbotbff as "chatbot BFF"
|
||||||
|
component desktopbff as "desktop BFF"
|
||||||
|
}
|
||||||
|
node intranetserv as "intranet services server"{
|
||||||
|
component ss as "supplier service API"
|
||||||
|
}
|
||||||
|
cloud onlypublic as "public cloud"{
|
||||||
|
component cas as "customer authentication service API"
|
||||||
|
component cs as "cart service API"
|
||||||
|
}
|
||||||
|
cloud cloudserv as "managed cloud"{
|
||||||
|
component os as "order service API"
|
||||||
|
}
|
||||||
|
iosapp -- iosbff
|
||||||
|
androidapp -- androidbff
|
||||||
|
chatbot -- chatbotbff
|
||||||
|
desktop -- desktopbff
|
||||||
|
iosbff -- cas
|
||||||
|
androidbff -- cas
|
||||||
|
iosbff -- cs
|
||||||
|
androidbff -- cs
|
||||||
|
iosbff -- os
|
||||||
|
androidbff -- os
|
||||||
|
chatbotbff -- os
|
||||||
|
desktopbff -- os
|
||||||
|
chatbotbff -- ss
|
||||||
|
desktopbff -- ss
|
||||||
|
```
|
||||||
|
|
||||||
|
This example implements a simplified version of the diagram above with two client-facing BFFs
|
||||||
|
instead of four, to keep the demo focused: a **Mobile BFF** standing in for the ios/android BFFs,
|
||||||
|
and a **Desktop BFF** standing in for the desktop/chatbot BFFs. Both call into the same shared
|
||||||
|
downstream services (`AuthService`, `OrderService`), while `CartService` is only used by the
|
||||||
|
Mobile BFF and `SupplierService` is only reachable from the Desktop BFF, matching the fan-out
|
||||||
|
shown in the diagram.
|
||||||
|
|
||||||
|
## Class Diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## When to Use the Backends For Frontends Pattern in Java
|
||||||
|
|
||||||
|
* Different client types (mobile, web, desktop, voice/chat) need meaningfully different shapes,
|
||||||
|
granularity, or aggregation of the same underlying data.
|
||||||
|
* A single shared API has grown a large number of client-specific conditional branches, optional
|
||||||
|
fields, or query parameters to accommodate every consumer.
|
||||||
|
* Different client teams need to iterate on their own API independently without coordinating
|
||||||
|
changes through one shared backend team.
|
||||||
|
* Some clients (e.g. mobile) need aggressively trimmed payloads for bandwidth/latency reasons,
|
||||||
|
while others (e.g. an internal desktop tool) need much richer data.
|
||||||
|
|
||||||
|
## Benefits and Trade-offs of Backends For Frontends Pattern
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Each client gets an API optimized for its own needs, improving performance and simplicity on
|
||||||
|
the client side.
|
||||||
|
* Client teams can evolve their BFF independently, reducing cross-team coordination.
|
||||||
|
* Downstream microservices stay generic and reusable; client-specific logic lives in the BFF
|
||||||
|
layer instead of leaking into shared services.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* Introduces additional services to build, deploy, and operate.
|
||||||
|
* Logic that is genuinely shared across clients can end up duplicated across BFFs if not
|
||||||
|
carefully factored out.
|
||||||
|
* Adds an extra network hop between the client and the downstream services.
|
||||||
|
|
||||||
|
## How to Implement Backends For Frontends Pattern in Java
|
||||||
|
|
||||||
|
1. Identify the distinct client types that need meaningfully different data shapes.
|
||||||
|
2. Define the downstream services each client's data actually depends on (`AuthService`,
|
||||||
|
`CartService`, `OrderService`, `SupplierService` in this example).
|
||||||
|
3. Create one BFF per client type, implementing a shared `ClientBff<T>` contract, where each BFF
|
||||||
|
only depends on the downstream services its client needs.
|
||||||
|
4. Have each BFF aggregate and reshape the downstream data into a response DTO tailored to its
|
||||||
|
client (`MobileDashboardResponse`, `DesktopDashboardResponse`).
|
||||||
|
5. Wire the client applications to call their own BFF rather than the downstream services
|
||||||
|
directly.
|
||||||
|
|
||||||
|
## Source Code
|
||||||
|
|
||||||
|
* [Pattern: Backends For Frontends](https://samnewman.io/patterns/architectural/bff/) by Sam Newman
|
||||||
|
* [Microservices Patterns: With examples in Java](https://www.amazon.com/Microservices-Patterns-examples-Chris-Richardson/dp/1617294543) by Chris Richardson
|
||||||
|
|
||||||
|
## References and Credits
|
||||||
|
|
||||||
|
* [Building Microservices](https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/) by Sam Newman
|
||||||
|
* [Pattern: Backend for frontend (microservices.io)](https://microservices.io/patterns/apigateway.html)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,130 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.bff {
|
||||||
|
class App {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- USER_ID : String {static}
|
||||||
|
- PRODUCT_ID : String {static}
|
||||||
|
- DEMO_PRICE_USD : double {static}
|
||||||
|
- DEMO_STOCK_LEVEL : int {static}
|
||||||
|
+ App()
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.bff.bff {
|
||||||
|
interface ClientBff<T> {
|
||||||
|
+ getDashboard(userId : String) : T {abstract}
|
||||||
|
}
|
||||||
|
class DesktopBff {
|
||||||
|
- authService : AuthService
|
||||||
|
- orderService : OrderService
|
||||||
|
- supplierService : SupplierService
|
||||||
|
+ DesktopBff(auth : AuthService, orders : OrderService, suppliers : SupplierService)
|
||||||
|
+ getDashboard(userId : String) : DesktopDashboardResponse
|
||||||
|
}
|
||||||
|
class MobileBff {
|
||||||
|
- MAX_RECENT_ORDERS : int {static}
|
||||||
|
- authService : AuthService
|
||||||
|
- cartService : CartService
|
||||||
|
- orderService : OrderService
|
||||||
|
+ MobileBff(auth : AuthService, cart : CartService, orders : OrderService)
|
||||||
|
+ getDashboard(userId : String) : MobileDashboardResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.bff.dto {
|
||||||
|
class DesktopDashboardResponse {
|
||||||
|
- greeting : String
|
||||||
|
- loyaltyTier : String
|
||||||
|
- orderStatuses : List<String>
|
||||||
|
- supplierStockSummaries : List<String>
|
||||||
|
+ DesktopDashboardResponse(greeting : String, loyaltyTier : String, orderStatuses : List<String>, supplierStockSummaries : List<String>)
|
||||||
|
}
|
||||||
|
class MobileDashboardResponse {
|
||||||
|
- greeting : String
|
||||||
|
- cartItemCount : int
|
||||||
|
- cartTotalUsd : double
|
||||||
|
- recentOrderSummaries : List<String>
|
||||||
|
+ MobileDashboardResponse(greeting : String, cartItemCount : int, cartTotalUsd : double, recentOrderSummaries : List<String>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.bff.model {
|
||||||
|
class CartItem {
|
||||||
|
- product : Product
|
||||||
|
- quantity : int
|
||||||
|
+ CartItem(product : Product, quantity : int)
|
||||||
|
+ lineTotal() : double
|
||||||
|
}
|
||||||
|
class Order {
|
||||||
|
- id : String
|
||||||
|
- productName : String
|
||||||
|
- status : String
|
||||||
|
+ Order(id : String, productName : String, status : String)
|
||||||
|
}
|
||||||
|
class Product {
|
||||||
|
- id : String
|
||||||
|
- name : String
|
||||||
|
- priceUsd : double
|
||||||
|
+ Product(id : String, name : String, priceUsd : double)
|
||||||
|
}
|
||||||
|
class SupplierRecord {
|
||||||
|
- productId : String
|
||||||
|
- supplierName : String
|
||||||
|
- stockLevel : int
|
||||||
|
+ SupplierRecord(productId : String, supplierName : String, stockLevel : int)
|
||||||
|
}
|
||||||
|
class User {
|
||||||
|
- id : String
|
||||||
|
- displayName : String
|
||||||
|
- loyaltyTier : String
|
||||||
|
+ User(id : String, displayName : String, loyaltyTier : String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.bff.service {
|
||||||
|
interface AuthService {
|
||||||
|
+ getUser(userId : String) : User {abstract}
|
||||||
|
}
|
||||||
|
interface CartService {
|
||||||
|
+ getCart(userId : String) : List<CartItem> {abstract}
|
||||||
|
}
|
||||||
|
interface OrderService {
|
||||||
|
+ getOrders(userId : String) : List<Order> {abstract}
|
||||||
|
}
|
||||||
|
interface SupplierService {
|
||||||
|
+ getSupplierRecords(productId : String) : List<SupplierRecord> {abstract}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.bff.service.impl {
|
||||||
|
class InMemoryAuthService {
|
||||||
|
- users : Map<String, User>
|
||||||
|
+ InMemoryAuthService(userData : Map<String, User>)
|
||||||
|
+ getUser(userId : String) : User
|
||||||
|
}
|
||||||
|
class InMemoryCartService {
|
||||||
|
- cartsByUserId : Map<String, List<CartItem>>
|
||||||
|
+ InMemoryCartService(carts : Map<String, List<CartItem>>)
|
||||||
|
+ getCart(userId : String) : List<CartItem>
|
||||||
|
}
|
||||||
|
class InMemoryOrderService {
|
||||||
|
- ordersByUserId : Map<String, List<Order>>
|
||||||
|
+ InMemoryOrderService(orders : Map<String, List<Order>>)
|
||||||
|
+ getOrders(userId : String) : List<Order>
|
||||||
|
}
|
||||||
|
class InMemorySupplierService {
|
||||||
|
- recordsByProductId : Map<String, List<SupplierRecord>>
|
||||||
|
+ InMemorySupplierService(records : Map<String, List<SupplierRecord>>)
|
||||||
|
+ getSupplierRecords(productId : String) : List<SupplierRecord>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopBff ..|> ClientBff
|
||||||
|
MobileBff ..|> ClientBff
|
||||||
|
DesktopBff --> "-authService" AuthService
|
||||||
|
DesktopBff --> "-orderService" OrderService
|
||||||
|
DesktopBff --> "-supplierService" SupplierService
|
||||||
|
MobileBff --> "-authService" AuthService
|
||||||
|
MobileBff --> "-cartService" CartService
|
||||||
|
MobileBff --> "-orderService" OrderService
|
||||||
|
InMemoryAuthService ..|> AuthService
|
||||||
|
InMemoryCartService ..|> CartService
|
||||||
|
InMemoryOrderService ..|> OrderService
|
||||||
|
InMemorySupplierService ..|> SupplierService
|
||||||
|
CartItem --> "-product" Product
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>backends-for-frontends</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.bff.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.bff.DesktopBff;
|
||||||
|
import com.iluwatar.bff.bff.MobileBff;
|
||||||
|
import com.iluwatar.bff.model.CartItem;
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import com.iluwatar.bff.model.Product;
|
||||||
|
import com.iluwatar.bff.model.SupplierRecord;
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryAuthService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryCartService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryOrderService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemorySupplierService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link App} demonstrates the Backends For Frontends (BFF) pattern.
|
||||||
|
*
|
||||||
|
* <p>A single set of downstream microservices (customer authentication, cart, order and supplier
|
||||||
|
* services) is shared by every client. Two different client-facing gateways -- {@link MobileBff}
|
||||||
|
* for the mobile apps and {@link DesktopBff} for the intranet desktop/chatbot clients -- each call
|
||||||
|
* a different subset of those services and reshape the results into a response tailored to what
|
||||||
|
* their own client actually needs, rather than exposing one one-size-fits-all API to every client.
|
||||||
|
*/
|
||||||
|
public final class App {
|
||||||
|
|
||||||
|
/** Logger for this class. */
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/** User id used in the demonstration. */
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
/** Product id used in the demonstration. */
|
||||||
|
private static final String PRODUCT_ID = "p-42";
|
||||||
|
|
||||||
|
/** Unit price of the demo product in US dollars. */
|
||||||
|
private static final double DEMO_PRICE_USD = 79.99;
|
||||||
|
|
||||||
|
/** Supplier stock level used in the demonstration. */
|
||||||
|
private static final int DEMO_STOCK_LEVEL = 120;
|
||||||
|
|
||||||
|
private App() {
|
||||||
|
// utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*
|
||||||
|
* @param args no argument sent
|
||||||
|
*/
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
// shared downstream microservices, as drawn in the pattern diagram
|
||||||
|
var authService = new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Alice", "GOLD")));
|
||||||
|
|
||||||
|
var product = new Product(PRODUCT_ID, "Wireless Headphones", DEMO_PRICE_USD);
|
||||||
|
var cartService = new InMemoryCartService(Map.of(USER_ID, List.of(new CartItem(product, 2))));
|
||||||
|
|
||||||
|
var orderService =
|
||||||
|
new InMemoryOrderService(
|
||||||
|
Map.of(
|
||||||
|
USER_ID,
|
||||||
|
List.of(
|
||||||
|
new Order("o-1", "Wireless Headphones", "DELIVERED"),
|
||||||
|
new Order("o-2", "USB-C Cable", "IN_TRANSIT"))));
|
||||||
|
|
||||||
|
var supplierService =
|
||||||
|
new InMemorySupplierService(
|
||||||
|
Map.of(
|
||||||
|
"Wireless Headphones",
|
||||||
|
List.of(new SupplierRecord(PRODUCT_ID, "Acme Audio Co.", DEMO_STOCK_LEVEL))));
|
||||||
|
|
||||||
|
// client-specific BFFs, each calling only the services their client needs
|
||||||
|
var mobileBff = new MobileBff(authService, cartService, orderService);
|
||||||
|
var desktopBff = new DesktopBff(authService, orderService, supplierService);
|
||||||
|
|
||||||
|
var mobileResponse = mobileBff.getDashboard(USER_ID);
|
||||||
|
LOGGER.info("Mobile BFF response: {}", mobileResponse);
|
||||||
|
|
||||||
|
var desktopResponse = desktopBff.getDashboard(USER_ID);
|
||||||
|
LOGGER.info("Desktop BFF response: {}", desktopResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.bff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common contract every client-specific Backend For Frontend implements: given a user id, build
|
||||||
|
* whatever response shape ({@code T}) that particular client needs. Each implementation is free to
|
||||||
|
* call a different subset of downstream services and aggregate them differently -- that freedom is
|
||||||
|
* the entire point of the pattern.
|
||||||
|
*
|
||||||
|
* @param <T> the response DTO shape this BFF returns to its client
|
||||||
|
*/
|
||||||
|
public interface ClientBff<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the dashboard response for a given user, tailored to this BFF's client.
|
||||||
|
*
|
||||||
|
* @param userId identifier of the user requesting their dashboard
|
||||||
|
* @return the client-specific response
|
||||||
|
*/
|
||||||
|
T getDashboard(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.bff;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.dto.DesktopDashboardResponse;
|
||||||
|
import com.iluwatar.bff.service.AuthService;
|
||||||
|
import com.iluwatar.bff.service.OrderService;
|
||||||
|
import com.iluwatar.bff.service.SupplierService;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend For Frontend serving the intranet clients (desktop app, chatbot) from the diagram. It
|
||||||
|
* aggregates the auth, order and supplier services and reshapes the results into a richer,
|
||||||
|
* back-office style payload. Unlike {@link MobileBff}, it does not touch the cart service at all
|
||||||
|
* and it does reach into the intranet-only supplier service, matching the fan-out drawn in the
|
||||||
|
* pattern diagram.
|
||||||
|
*/
|
||||||
|
public final class DesktopBff implements ClientBff<DesktopDashboardResponse> {
|
||||||
|
|
||||||
|
/** The customer authentication service. */
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
/** The order service. */
|
||||||
|
private final OrderService orderService;
|
||||||
|
|
||||||
|
/** The supplier service (intranet-only). */
|
||||||
|
private final SupplierService supplierService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a desktop BFF wired to the downstream services it depends on.
|
||||||
|
*
|
||||||
|
* @param auth the customer authentication service
|
||||||
|
* @param orders the order service
|
||||||
|
* @param suppliers the supplier service
|
||||||
|
*/
|
||||||
|
public DesktopBff(
|
||||||
|
final AuthService auth, final OrderService orders, final SupplierService suppliers) {
|
||||||
|
this.authService = auth;
|
||||||
|
this.orderService = orders;
|
||||||
|
this.supplierService = suppliers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DesktopDashboardResponse getDashboard(final String userId) {
|
||||||
|
var user = authService.getUser(userId);
|
||||||
|
var orders = orderService.getOrders(userId);
|
||||||
|
|
||||||
|
var orderStatuses =
|
||||||
|
orders.stream()
|
||||||
|
.map(order -> order.id() + ": " + order.productName() + " [" + order.status() + "]")
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
var supplierStockSummaries = new ArrayList<String>();
|
||||||
|
for (var order : orders) {
|
||||||
|
for (var supplierRecord : supplierService.getSupplierRecords(order.productName())) {
|
||||||
|
supplierStockSummaries.add(
|
||||||
|
supplierRecord.supplierName()
|
||||||
|
+ ": "
|
||||||
|
+ supplierRecord.stockLevel()
|
||||||
|
+ " units of "
|
||||||
|
+ order.productName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DesktopDashboardResponse(
|
||||||
|
"Welcome back, " + user.displayName(),
|
||||||
|
user.loyaltyTier(),
|
||||||
|
List.copyOf(orderStatuses),
|
||||||
|
List.copyOf(supplierStockSummaries));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.bff;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.dto.MobileDashboardResponse;
|
||||||
|
import com.iluwatar.bff.service.AuthService;
|
||||||
|
import com.iluwatar.bff.service.CartService;
|
||||||
|
import com.iluwatar.bff.service.OrderService;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend For Frontend serving the mobile clients (iOS app, Android app) from the diagram. It
|
||||||
|
* aggregates the auth, cart and order services and reshapes the results into a small payload suited
|
||||||
|
* to a phone screen and a limited-bandwidth connection. It never calls the supplier service: a
|
||||||
|
* mobile shopper has no use for back-office stock data, so this BFF simply does not expose it,
|
||||||
|
* rather than sending it and letting the client ignore it.
|
||||||
|
*/
|
||||||
|
public final class MobileBff implements ClientBff<MobileDashboardResponse> {
|
||||||
|
|
||||||
|
/** Maximum number of recent order summaries to include in the mobile response. */
|
||||||
|
private static final int MAX_RECENT_ORDERS = 3;
|
||||||
|
|
||||||
|
/** The customer authentication service. */
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
/** The cart service. */
|
||||||
|
private final CartService cartService;
|
||||||
|
|
||||||
|
/** The order service. */
|
||||||
|
private final OrderService orderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mobile BFF wired to the downstream services it depends on.
|
||||||
|
*
|
||||||
|
* @param auth the customer authentication service
|
||||||
|
* @param cart the cart service
|
||||||
|
* @param orders the order service
|
||||||
|
*/
|
||||||
|
public MobileBff(final AuthService auth, final CartService cart, final OrderService orders) {
|
||||||
|
this.authService = auth;
|
||||||
|
this.cartService = cart;
|
||||||
|
this.orderService = orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MobileDashboardResponse getDashboard(final String userId) {
|
||||||
|
var user = authService.getUser(userId);
|
||||||
|
var cart = cartService.getCart(userId);
|
||||||
|
var orders = orderService.getOrders(userId);
|
||||||
|
|
||||||
|
var cartTotal = cart.stream().mapToDouble(item -> item.lineTotal()).sum();
|
||||||
|
var recentOrderSummaries =
|
||||||
|
orders.stream()
|
||||||
|
.limit(MAX_RECENT_ORDERS)
|
||||||
|
.map(order -> order.productName() + " (" + order.status() + ")")
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return new MobileDashboardResponse(
|
||||||
|
"Hi " + user.displayName() + "!",
|
||||||
|
cart.size(),
|
||||||
|
cartTotal,
|
||||||
|
List.copyOf(recentOrderSummaries));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Client-specific Backend For Frontend implementations: one per client type (mobile, desktop) that
|
||||||
|
* each aggregate a different subset of downstream services.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff.bff;
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of data the desktop back-office client renders: richer than the mobile payload,
|
||||||
|
* including full order status and supplier stock levels that a mobile shopper never needs.
|
||||||
|
*
|
||||||
|
* @param greeting a short personalized greeting for the user
|
||||||
|
* @param loyaltyTier the user's loyalty program tier
|
||||||
|
* @param orderStatuses detailed "id: productName [status]" lines for every order
|
||||||
|
* @param supplierStockSummaries "supplierName: stockLevel units" lines for relevant products
|
||||||
|
*/
|
||||||
|
public record DesktopDashboardResponse(
|
||||||
|
String greeting,
|
||||||
|
String loyaltyTier,
|
||||||
|
List<String> orderStatuses,
|
||||||
|
List<String> supplierStockSummaries) {}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of data the mobile (iOS/Android) client actually renders: a lean payload with just
|
||||||
|
* enough for a small screen, deliberately excluding fields the desktop client needs.
|
||||||
|
*
|
||||||
|
* @param greeting a short personalized greeting for the user
|
||||||
|
* @param cartItemCount number of items currently in the user's cart
|
||||||
|
* @param cartTotalUsd total value of the user's cart in US dollars
|
||||||
|
* @param recentOrderSummaries short human-readable summaries of the user's most recent orders
|
||||||
|
*/
|
||||||
|
public record MobileDashboardResponse(
|
||||||
|
String greeting, int cartItemCount, double cartTotalUsd, List<String> recentOrderSummaries) {}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Response DTOs shaped by each BFF for its specific client (mobile dashboard, desktop dashboard).
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff.dto;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single line item inside a user's shopping cart, as returned by the cart service API.
|
||||||
|
*
|
||||||
|
* @param product the product being purchased
|
||||||
|
* @param quantity number of units of the product
|
||||||
|
*/
|
||||||
|
public record CartItem(Product product, int quantity) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the line total for this cart item.
|
||||||
|
*
|
||||||
|
* @return quantity multiplied by unit price
|
||||||
|
*/
|
||||||
|
public double lineTotal() {
|
||||||
|
return quantity * product.priceUsd();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A past order returned by the order service API.
|
||||||
|
*
|
||||||
|
* @param id order identifier
|
||||||
|
* @param productName name of the ordered product
|
||||||
|
* @param status current fulfillment status, e.g. "DELIVERED", "IN_TRANSIT"
|
||||||
|
*/
|
||||||
|
public record Order(String id, String productName, String status) {}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple product record returned by the downstream Cart/Order services. Represents the data a
|
||||||
|
* catalog or cart entry carries before each BFF trims or reshapes it for its own client.
|
||||||
|
*
|
||||||
|
* @param id product identifier
|
||||||
|
* @param name display name of the product
|
||||||
|
* @param priceUsd unit price in US dollars
|
||||||
|
*/
|
||||||
|
public record Product(String id, String name, double priceUsd) {}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A supplier stock record returned by the supplier service API, used by the desktop back-office
|
||||||
|
* client to show inventory information that mobile customers never need to see.
|
||||||
|
*
|
||||||
|
* @param productId identifier of the product this record refers to
|
||||||
|
* @param supplierName name of the supplying vendor
|
||||||
|
* @param stockLevel units currently available from this supplier
|
||||||
|
*/
|
||||||
|
public record SupplierRecord(String productId, String supplierName, int stockLevel) {}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticated user profile returned by the customer authentication service API. Kept
|
||||||
|
* intentionally minimal; each BFF decides which of these fields its client actually needs.
|
||||||
|
*
|
||||||
|
* @param id unique user identifier
|
||||||
|
* @param displayName human-readable name of the user
|
||||||
|
* @param loyaltyTier loyalty program tier, e.g. "GOLD", "SILVER", "STANDARD"
|
||||||
|
*/
|
||||||
|
public record User(String id, String displayName, String loyaltyTier) {}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Domain model records shared across downstream services: User, Product, CartItem, Order, and
|
||||||
|
* SupplierRecord. Each BFF consumes these raw domain objects and reshapes them into its own
|
||||||
|
* client-specific DTO.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff.model;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Backends For Frontends pattern: a dedicated gateway per client type (mobile, desktop) that
|
||||||
|
* aggregates only the downstream services each client actually needs.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the customer authentication service API from the diagram: a downstream microservice
|
||||||
|
* shared by every client-specific BFF. Each BFF calls this the same way; only what they do with the
|
||||||
|
* result differs.
|
||||||
|
*/
|
||||||
|
public interface AuthService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the authenticated user profile for the given id.
|
||||||
|
*
|
||||||
|
* @param userId identifier of the user to look up
|
||||||
|
* @return the matching {@link User}
|
||||||
|
*/
|
||||||
|
User getUser(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.CartItem;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the cart service API from the diagram. In this example only the mobile and android
|
||||||
|
* clients need cart data, so only {@link com.iluwatar.bff.bff.MobileBff} calls this service.
|
||||||
|
*/
|
||||||
|
public interface CartService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current cart contents for a user.
|
||||||
|
*
|
||||||
|
* @param userId identifier of the user whose cart is requested
|
||||||
|
* @return the list of items currently in the user's cart
|
||||||
|
*/
|
||||||
|
List<CartItem> getCart(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the order service API from the diagram. This downstream service is shared by every
|
||||||
|
* client-specific BFF, matching the fan-out shown for "order service API" in the pattern diagram.
|
||||||
|
*/
|
||||||
|
public interface OrderService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the order history for a user.
|
||||||
|
*
|
||||||
|
* @param userId identifier of the user whose orders are requested
|
||||||
|
* @return the list of past orders for the user
|
||||||
|
*/
|
||||||
|
List<Order> getOrders(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.SupplierRecord;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the supplier service API from the diagram, reachable only from the intranet services
|
||||||
|
* server. Only back-office style clients (desktop app, chatbot) call this service.
|
||||||
|
*/
|
||||||
|
public interface SupplierService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves supplier stock records for a product.
|
||||||
|
*
|
||||||
|
* @param productName name of the product to check stock for
|
||||||
|
* @return the list of supplier stock records for the product
|
||||||
|
*/
|
||||||
|
List<SupplierRecord> getSupplierRecords(String productName);
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service.impl;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
import com.iluwatar.bff.service.AuthService;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory stand-in for the real customer authentication service API. Real deployments would
|
||||||
|
* replace this with an HTTP/gRPC client; the BFFs are written against the {@link AuthService}
|
||||||
|
* interface, so swapping the implementation later requires no change to any BFF.
|
||||||
|
*/
|
||||||
|
public final class InMemoryAuthService implements AuthService {
|
||||||
|
|
||||||
|
/** Users stored in memory, keyed by user id. */
|
||||||
|
private final Map<String, User> users;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the service with a fixed backing map of users, keyed by user id.
|
||||||
|
*
|
||||||
|
* @param userData the user data this service serves
|
||||||
|
*/
|
||||||
|
public InMemoryAuthService(final Map<String, User> userData) {
|
||||||
|
this.users = userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUser(final String userId) {
|
||||||
|
var user = users.get(userId);
|
||||||
|
if (user == null) {
|
||||||
|
throw new IllegalArgumentException("Unknown user id: " + userId);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service.impl;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.CartItem;
|
||||||
|
import com.iluwatar.bff.service.CartService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** In-memory stand-in for the real cart service API. */
|
||||||
|
public final class InMemoryCartService implements CartService {
|
||||||
|
|
||||||
|
/** Carts stored in memory, keyed by user id. */
|
||||||
|
private final Map<String, List<CartItem>> cartsByUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the service with a fixed backing map of carts, keyed by user id.
|
||||||
|
*
|
||||||
|
* @param carts the cart data this service serves
|
||||||
|
*/
|
||||||
|
public InMemoryCartService(final Map<String, List<CartItem>> carts) {
|
||||||
|
this.cartsByUserId = carts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CartItem> getCart(final String userId) {
|
||||||
|
return cartsByUserId.getOrDefault(userId, List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service.impl;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import com.iluwatar.bff.service.OrderService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** In-memory stand-in for the real order service API. */
|
||||||
|
public final class InMemoryOrderService implements OrderService {
|
||||||
|
|
||||||
|
/** Order histories stored in memory, keyed by user id. */
|
||||||
|
private final Map<String, List<Order>> ordersByUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the service with a fixed backing map of order histories, keyed by user id.
|
||||||
|
*
|
||||||
|
* @param orders the order data this service serves
|
||||||
|
*/
|
||||||
|
public InMemoryOrderService(final Map<String, List<Order>> orders) {
|
||||||
|
this.ordersByUserId = orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Order> getOrders(final String userId) {
|
||||||
|
return ordersByUserId.getOrDefault(userId, List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.service.impl;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.SupplierRecord;
|
||||||
|
import com.iluwatar.bff.service.SupplierService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** In-memory stand-in for the real supplier service API, reachable only from the intranet. */
|
||||||
|
public final class InMemorySupplierService implements SupplierService {
|
||||||
|
|
||||||
|
/** Supplier records stored in memory, keyed by product name. */
|
||||||
|
private final Map<String, List<SupplierRecord>> recordsByProductName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the service with a fixed backing map of supplier records, keyed by product name.
|
||||||
|
*
|
||||||
|
* @param records the supplier data this service serves
|
||||||
|
*/
|
||||||
|
public InMemorySupplierService(final Map<String, List<SupplierRecord>> records) {
|
||||||
|
this.recordsByProductName = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SupplierRecord> getSupplierRecords(final String productName) {
|
||||||
|
return recordsByProductName.getOrDefault(productName, List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* In-memory implementations of the downstream service API interfaces, used for the pattern
|
||||||
|
* demonstration. Production deployments would replace these with real HTTP/gRPC clients.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff.service.impl;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Downstream service API interfaces shared by all BFFs: AuthService, CartService, OrderService, and
|
||||||
|
* SupplierService. Each BFF wires in only the services its client requires.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.bff.service;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that {@link App}'s demo entry point runs end-to-end without throwing, following the
|
||||||
|
* convention used throughout this repository for pattern demo apps.
|
||||||
|
*/
|
||||||
|
class AppTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mainShouldRunWithoutException() {
|
||||||
|
assertDoesNotThrow(() -> App.main(new String[] {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.bff;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import com.iluwatar.bff.model.SupplierRecord;
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryAuthService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryOrderService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemorySupplierService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link DesktopBff}. */
|
||||||
|
class DesktopBffTest {
|
||||||
|
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAggregateOrdersAndSupplierStockIntoDesktopShape() {
|
||||||
|
var authService = new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Alice", "GOLD")));
|
||||||
|
var orderService =
|
||||||
|
new InMemoryOrderService(
|
||||||
|
Map.of(USER_ID, List.of(new Order("o-1", "Headphones", "DELIVERED"))));
|
||||||
|
var supplierService =
|
||||||
|
new InMemorySupplierService(
|
||||||
|
Map.of("Headphones", List.of(new SupplierRecord("p-1", "Acme Audio", 30))));
|
||||||
|
|
||||||
|
var bff = new DesktopBff(authService, orderService, supplierService);
|
||||||
|
var response = bff.getDashboard(USER_ID);
|
||||||
|
|
||||||
|
assertEquals("Welcome back, Alice", response.greeting());
|
||||||
|
assertEquals("GOLD", response.loyaltyTier());
|
||||||
|
assertTrue(response.orderStatuses().get(0).contains("Headphones"));
|
||||||
|
assertTrue(response.supplierStockSummaries().get(0).contains("Acme Audio"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnNoSupplierSummariesWhenNoOrdersExist() {
|
||||||
|
var authService =
|
||||||
|
new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Carol", "STANDARD")));
|
||||||
|
var orderService = new InMemoryOrderService(Map.of(USER_ID, List.of()));
|
||||||
|
var supplierService = new InMemorySupplierService(Map.of());
|
||||||
|
|
||||||
|
var bff = new DesktopBff(authService, orderService, supplierService);
|
||||||
|
var response = bff.getDashboard(USER_ID);
|
||||||
|
|
||||||
|
assertEquals(0, response.orderStatuses().size());
|
||||||
|
assertEquals(0, response.supplierStockSummaries().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.bff.bff;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.CartItem;
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import com.iluwatar.bff.model.Product;
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryAuthService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryCartService;
|
||||||
|
import com.iluwatar.bff.service.impl.InMemoryOrderService;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link MobileBff}. */
|
||||||
|
class MobileBffTest {
|
||||||
|
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAggregateCartAndOrdersIntoMobileShape() {
|
||||||
|
var authService = new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Alice", "GOLD")));
|
||||||
|
var product = new Product("p-1", "Headphones", 50.0);
|
||||||
|
var cartService = new InMemoryCartService(Map.of(USER_ID, List.of(new CartItem(product, 2))));
|
||||||
|
var orderService =
|
||||||
|
new InMemoryOrderService(
|
||||||
|
Map.of(USER_ID, List.of(new Order("o-1", "Headphones", "DELIVERED"))));
|
||||||
|
|
||||||
|
var bff = new MobileBff(authService, cartService, orderService);
|
||||||
|
var response = bff.getDashboard(USER_ID);
|
||||||
|
|
||||||
|
assertEquals("Hi Alice!", response.greeting());
|
||||||
|
assertEquals(1, response.cartItemCount());
|
||||||
|
assertEquals(100.0, response.cartTotalUsd());
|
||||||
|
assertTrue(response.recentOrderSummaries().get(0).contains("Headphones"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCapRecentOrderSummariesAtThree() {
|
||||||
|
var authService = new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Bob", "SILVER")));
|
||||||
|
var cartService = new InMemoryCartService(Map.of(USER_ID, List.of()));
|
||||||
|
var orderService =
|
||||||
|
new InMemoryOrderService(
|
||||||
|
Map.of(
|
||||||
|
USER_ID,
|
||||||
|
List.of(
|
||||||
|
new Order("o-1", "A", "DELIVERED"),
|
||||||
|
new Order("o-2", "B", "DELIVERED"),
|
||||||
|
new Order("o-3", "C", "DELIVERED"),
|
||||||
|
new Order("o-4", "D", "DELIVERED"))));
|
||||||
|
|
||||||
|
var bff = new MobileBff(authService, cartService, orderService);
|
||||||
|
var response = bff.getDashboard(USER_ID);
|
||||||
|
|
||||||
|
assertEquals(3, response.recentOrderSummaries().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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 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.bff.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.User;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link InMemoryAuthService}. */
|
||||||
|
class InMemoryAuthServiceTest {
|
||||||
|
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUserForKnownId() {
|
||||||
|
var expected = new User(USER_ID, "Alice", "GOLD");
|
||||||
|
var service = new InMemoryAuthService(Map.of(USER_ID, expected));
|
||||||
|
|
||||||
|
var actual = service.getUser(USER_ID);
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowForUnknownId() {
|
||||||
|
var service = new InMemoryAuthService(Map.of(USER_ID, new User(USER_ID, "Alice", "GOLD")));
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> service.getUser("unknown-id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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 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.bff.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.CartItem;
|
||||||
|
import com.iluwatar.bff.model.Product;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link InMemoryCartService}. */
|
||||||
|
class InMemoryCartServiceTest {
|
||||||
|
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnCartItemsForKnownUser() {
|
||||||
|
var product = new Product("p-1", "Headphones", 49.99);
|
||||||
|
var item = new CartItem(product, 2);
|
||||||
|
var service = new InMemoryCartService(Map.of(USER_ID, List.of(item)));
|
||||||
|
|
||||||
|
var cart = service.getCart(USER_ID);
|
||||||
|
|
||||||
|
assertEquals(1, cart.size());
|
||||||
|
assertEquals(item, cart.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyListForUnknownUser() {
|
||||||
|
var service = new InMemoryCartService(Map.of());
|
||||||
|
|
||||||
|
var cart = service.getCart("unknown-user");
|
||||||
|
|
||||||
|
assertTrue(cart.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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 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.bff.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.Order;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link InMemoryOrderService}. */
|
||||||
|
class InMemoryOrderServiceTest {
|
||||||
|
|
||||||
|
private static final String USER_ID = "u-1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnOrdersForKnownUser() {
|
||||||
|
var order = new Order("o-1", "Headphones", "DELIVERED");
|
||||||
|
var service = new InMemoryOrderService(Map.of(USER_ID, List.of(order)));
|
||||||
|
|
||||||
|
var orders = service.getOrders(USER_ID);
|
||||||
|
|
||||||
|
assertEquals(1, orders.size());
|
||||||
|
assertEquals(order, orders.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyListForUnknownUser() {
|
||||||
|
var service = new InMemoryOrderService(Map.of());
|
||||||
|
|
||||||
|
var orders = service.getOrders("unknown-user");
|
||||||
|
|
||||||
|
assertTrue(orders.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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 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.bff.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.iluwatar.bff.model.SupplierRecord;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests for {@link InMemorySupplierService}. */
|
||||||
|
class InMemorySupplierServiceTest {
|
||||||
|
|
||||||
|
private static final String PRODUCT_NAME = "Headphones";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnSupplierRecordsForKnownProductName() {
|
||||||
|
var record = new SupplierRecord("p-1", "Acme Audio", 30);
|
||||||
|
var service = new InMemorySupplierService(Map.of(PRODUCT_NAME, List.of(record)));
|
||||||
|
|
||||||
|
var records = service.getSupplierRecords(PRODUCT_NAME);
|
||||||
|
|
||||||
|
assertEquals(1, records.size());
|
||||||
|
assertEquals(record, records.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyListForUnknownProductName() {
|
||||||
|
var service = new InMemorySupplierService(Map.of());
|
||||||
|
|
||||||
|
var records = service.getSupplierRecords("unknown-product");
|
||||||
|
|
||||||
|
assertTrue(records.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.projectreactor</groupId>
|
<groupId>io.projectreactor</groupId>
|
||||||
<artifactId>reactor-test</artifactId>
|
<artifactId>reactor-test</artifactId>
|
||||||
<version>3.8.0-M1</version>
|
<version>3.8.0-RC1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<version>3.27.3</version>
|
<version>3.27.7</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public final class App {
|
|||||||
/** Program entry point. */
|
/** Program entry point. */
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
var task = new SimpleTask();
|
var task = new SimpleTask();
|
||||||
task.executeWith(() -> LOGGER.info("I'm done now."));
|
|
||||||
|
LOGGER.info("=== Synchronous callback ===");
|
||||||
|
task.executeWith(() -> LOGGER.info("Sync callback executed."));
|
||||||
|
|
||||||
|
LOGGER.info("=== Asynchronous callback ===");
|
||||||
|
task.executeAsyncWith(() -> LOGGER.info("Async callback executed.")).join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
package com.iluwatar.callback;
|
package com.iluwatar.callback;
|
||||||
|
|
||||||
/** Callback interface. */
|
/** Callback interface. */
|
||||||
|
@FunctionalInterface
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
void call();
|
void call();
|
||||||
|
|||||||
@@ -25,15 +25,30 @@
|
|||||||
package com.iluwatar.callback;
|
package com.iluwatar.callback;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/** Template-method class for callback hook execution. */
|
/**
|
||||||
|
* Template-method class for callback hook execution.
|
||||||
|
*
|
||||||
|
* <p>Provides both synchronous and asynchronous execution with callback support.
|
||||||
|
*/
|
||||||
public abstract class Task {
|
public abstract class Task {
|
||||||
|
|
||||||
/** Execute with callback. */
|
/** Execute the task and call the callback method synchronously upon completion. */
|
||||||
final void executeWith(Callback callback) {
|
final void executeWith(Callback callback) {
|
||||||
execute();
|
execute();
|
||||||
Optional.ofNullable(callback).ifPresent(Callback::call);
|
Optional.ofNullable(callback).ifPresent(Callback::call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Execute the task and asynchronously call the callback method upon completion. */
|
||||||
|
final CompletableFuture<Void> executeAsyncWith(Callback callback) {
|
||||||
|
return CompletableFuture.runAsync(
|
||||||
|
() -> {
|
||||||
|
execute();
|
||||||
|
Optional.ofNullable(callback).ifPresent(Callback::call);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Actual work to be implemented by subclasses. */
|
||||||
public abstract void execute();
|
public abstract void execute();
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,9 @@ package com.iluwatar.callback;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,19 +42,44 @@ class CallbackTest {
|
|||||||
private Integer callingCount = 0;
|
private Integer callingCount = 0;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test() {
|
void testSynchronousCallback() {
|
||||||
Callback callback = () -> callingCount++;
|
var counter = new AtomicInteger();
|
||||||
|
Callback callback = counter::incrementAndGet;
|
||||||
var task = new SimpleTask();
|
var task = new SimpleTask();
|
||||||
|
|
||||||
assertEquals(Integer.valueOf(0), callingCount, "Initial calling count of 0");
|
assertEquals(0, counter.get(), "Initial count should be 0");
|
||||||
|
|
||||||
task.executeWith(callback);
|
task.executeWith(callback);
|
||||||
|
assertEquals(1, counter.get(), "Callback should be called once");
|
||||||
assertEquals(Integer.valueOf(1), callingCount, "Callback called once");
|
|
||||||
|
|
||||||
task.executeWith(callback);
|
task.executeWith(callback);
|
||||||
|
assertEquals(2, counter.get(), "Callback should be called twice");
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(Integer.valueOf(2), callingCount, "Callback called twice");
|
@Test
|
||||||
|
void testAsynchronousCallback() {
|
||||||
|
var task = new SimpleTask();
|
||||||
|
|
||||||
|
var counter1 = new AtomicInteger();
|
||||||
|
final CompletableFuture<Void> future1 = new CompletableFuture<>();
|
||||||
|
Callback callback1 =
|
||||||
|
() -> {
|
||||||
|
counter1.incrementAndGet();
|
||||||
|
future1.complete(null);
|
||||||
|
};
|
||||||
|
var f1 = task.executeAsyncWith(callback1);
|
||||||
|
future1.orTimeout(1, TimeUnit.SECONDS).join();
|
||||||
|
f1.join();
|
||||||
|
assertEquals(1, counter1.get(), "Async callback should increment once");
|
||||||
|
|
||||||
|
var counter2 = new AtomicInteger();
|
||||||
|
final CompletableFuture<Void> future2 = new CompletableFuture<>();
|
||||||
|
Callback callback2 =
|
||||||
|
() -> {
|
||||||
|
counter2.incrementAndGet();
|
||||||
|
future2.complete(null);
|
||||||
|
};
|
||||||
|
var f2 = task.executeAsyncWith(callback2);
|
||||||
|
future2.orTimeout(1, TimeUnit.SECONDS).join();
|
||||||
|
f2.join();
|
||||||
|
assertEquals(1, counter2.get(), "Async callback should increment once again");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ The Circuit Breaker pattern is applicable:
|
|||||||
* Cloud-based services to gracefully handle the failure of external services
|
* Cloud-based services to gracefully handle the failure of external services
|
||||||
* E-commerce platforms to manage high volumes of transactions and dependency on external APIs
|
* E-commerce platforms to manage high volumes of transactions and dependency on external APIs
|
||||||
* Microservices architectures for maintaining system stability and responsiveness
|
* Microservices architectures for maintaining system stability and responsiveness
|
||||||
* [Spring Circuit Breaker module](https://spring.io/guides/gs/circuit-breaker)
|
* [Spring Circuit Breaker module](https://spring.io/guides/gs/cloud-circuit-breaker)
|
||||||
* [Netflix Hystrix API](https://github.com/Netflix/Hystrix)
|
* [Netflix Hystrix API](https://github.com/Netflix/Hystrix)
|
||||||
|
|
||||||
## Benefits and Trade-offs of Circuit Breaker Pattern
|
## Benefits and Trade-offs of Circuit Breaker Pattern
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<artifactId>commander</artifactId>
|
<artifactId>commander</artifactId>
|
||||||
<properties>
|
<properties>
|
||||||
<slf4j.version>2.0.17</slf4j.version>
|
<slf4j.version>2.0.18</slf4j.version>
|
||||||
<logback.version>1.5.6</logback.version>
|
<logback.version>1.5.6</logback.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -0,0 +1,360 @@
|
|||||||
|
---
|
||||||
|
title: "DAO Factory Pattern: Flexible Data Access Layer for Seamless Data Source Switching"
|
||||||
|
shortTitle: DAO Factory
|
||||||
|
description: "Learn the Data Access Object Pattern combine with Abstract Factory Pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge."
|
||||||
|
category: Structural
|
||||||
|
language: en
|
||||||
|
tag:
|
||||||
|
- Abstraction
|
||||||
|
- Data access
|
||||||
|
- Layer architecture
|
||||||
|
- Persistence
|
||||||
|
---
|
||||||
|
|
||||||
|
## Also known as
|
||||||
|
|
||||||
|
* DAO Factory
|
||||||
|
* Factory for Data Access Object strategy using Abstract Factory
|
||||||
|
|
||||||
|
|
||||||
|
## Intent of Data Access Object Factory Design Pattern
|
||||||
|
|
||||||
|
The DAO Factory combines the Data Access Object and Abstract Factory patterns to seperate business logic from data access logic, while increasing flexibility when switching between different data sources.
|
||||||
|
|
||||||
|
## Detailed Explanation of Data Access Object Factory Pattern with Real-World Examples
|
||||||
|
|
||||||
|
Real-world example
|
||||||
|
|
||||||
|
> A real-world analogy for the DAO Factory pattern is a multilingual customer service center. Imagine a bank that serves customers speaking different languages—English, French, and Spanish. When a customer calls, an automated system first detects the customer's preferred language, then routes the call to the appropriate support team that speaks that language. Each team follows the same company policies (standard procedures), but handles interactions in a language-specific way.
|
||||||
|
>
|
||||||
|
> In the same way, the DAO Factory pattern uses a factory to determine the correct set of DAO implementations based on the data source (e.g., MySQL, MongoDB). Each DAO factory returns a group of DAOs tailored to a specific data source, all conforming to the same interfaces. This allows the application to interact with any supported database in a consistent manner, without changing the business logic—just like how the customer service system handles multiple languages while following the same support protocols.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> The DAO Factory pattern abstracts the creation of Data Access Objects (DAOs), allowing you to request a specific DAO from a central factory without worrying about its underlying implementation. This makes the code easier to maintain and flexible to change, especially when switching between databases or storage mechanisms.
|
||||||
|
|
||||||
|
Wikipedia says
|
||||||
|
|
||||||
|
> The Data Access Object (DAO) design pattern is a structural pattern that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, the DAO provides some specific data operations without exposing details of the database. The DAO Factory is an extension of this concept, responsible for generating the required DAO implementations.
|
||||||
|
|
||||||
|
Class diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Programmatic Example of Data Access Object Factory in Java
|
||||||
|
|
||||||
|
In this example, the persistence object represents a Customer.
|
||||||
|
|
||||||
|
We are considering a flexible storage strategy where the application should be able to work with three different types of data sources: an H2 in-memory relational database (RDBMS), a MongoDB (object-oriented database), and a JSON flat file (flat file storage).
|
||||||
|
|
||||||
|
``` java
|
||||||
|
public enum DataSourceType {
|
||||||
|
H2,
|
||||||
|
Mongo,
|
||||||
|
FlatFile
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First, we define a Customer class that will be persisted in different storage systems. The ID field is generic to maintain compatibility with both relational and object-oriented databases.
|
||||||
|
|
||||||
|
``` java
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class Customer<T> implements Serializable {
|
||||||
|
private T id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we define a CustomerDAO interface that outlines the standard CRUD operations on the Customer model. This interface will have three concrete implementations, each corresponding to a specific data source: H2 in-memory database, MongoDB, and JSON file.
|
||||||
|
|
||||||
|
``` java
|
||||||
|
public interface CustomerDAO<T> {
|
||||||
|
|
||||||
|
void save(Customer<T> customer);
|
||||||
|
|
||||||
|
void update(Customer<T> customer);
|
||||||
|
|
||||||
|
void delete(T id);
|
||||||
|
|
||||||
|
List<Customer<T>> findAll();
|
||||||
|
|
||||||
|
Optional<Customer<T>> findById(T id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the implementations
|
||||||
|
|
||||||
|
``` java
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class H2CustomerDAO implements CustomerDAO<Long> {
|
||||||
|
private final DataSource dataSource;
|
||||||
|
private final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)";
|
||||||
|
private final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?";
|
||||||
|
private final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?";
|
||||||
|
private final String SELECT_CUSTOMER_BY_ID = "SELECT * FROM customer WHERE id= ?";
|
||||||
|
private final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer";
|
||||||
|
private final String CREATE_SCHEMA =
|
||||||
|
"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
|
||||||
|
private final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(Customer<Long> customer) {
|
||||||
|
// Implement operation save for H2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Customer<Long> customer) {
|
||||||
|
// Implement operation save for H2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Long id) {
|
||||||
|
// Implement operation delete for H2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Customer<Long>> findAll() {
|
||||||
|
// Implement operation find all for H2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Customer<Long>> findById(Long id) {
|
||||||
|
// Implement operation find by id for H2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` java
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MongoCustomerDAO implements CustomerDAO<ObjectId> {
|
||||||
|
private final MongoCollection<Document> customerCollection;
|
||||||
|
|
||||||
|
// Implement CRUD operation with MongoDB data source
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` java
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FlatFileCustomerDAO implements CustomerDAO<Long> {
|
||||||
|
private final Path filePath;
|
||||||
|
private final Gson gson;
|
||||||
|
Type customerListType = new TypeToken<List<Customer<Long>>>() {
|
||||||
|
}.getType();
|
||||||
|
|
||||||
|
// Implement CRUD operation with Flat file data source
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, we create an abstract class DAOFactory that defines two key methods: a static method getDataSource() and an abstract method createCustomerDAO().
|
||||||
|
|
||||||
|
- The getDataSource() method is a factory selector—it returns a concrete DAOFactory instance based on the type of data source requested.
|
||||||
|
|
||||||
|
- Each subclass of DAOFactory will implement the createCustomerDAO() method to provide the corresponding CustomerDAO implementation.
|
||||||
|
|
||||||
|
``` java
|
||||||
|
public abstract class DAOFactory {
|
||||||
|
public static DAOFactory getDataSource(DataSourceType dataSourceType) {
|
||||||
|
return switch (dataSourceType) {
|
||||||
|
case H2 -> new H2DataSourceFactory();
|
||||||
|
case Mongo -> new MongoDataSourceFactory();
|
||||||
|
case FlatFile -> new FlatFileDataSourceFactory();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract CustomerDAO createCustomerDAO();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We then implement three specific factory classes:
|
||||||
|
|
||||||
|
H2DataSourceFactory for H2 in-memory RDBMS
|
||||||
|
``` java
|
||||||
|
public class H2DataSourceFactory extends DAOFactory {
|
||||||
|
private final String DB_URL = "jdbc:h2:~/test";
|
||||||
|
private final String USER = "sa";
|
||||||
|
private final String PASS = "";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerDAO createCustomerDAO() {
|
||||||
|
return new H2CustomerDAO(createDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource createDataSource() {
|
||||||
|
var dataSource = new JdbcDataSource();
|
||||||
|
dataSource.setURL(DB_URL);
|
||||||
|
dataSource.setUser(USER);
|
||||||
|
dataSource.setPassword(PASS);
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
MongoDataSourceFactory for MongoDB
|
||||||
|
``` java
|
||||||
|
public class MongoDataSourceFactory extends DAOFactory {
|
||||||
|
private final String CONN_STR = "mongodb://localhost:27017/";
|
||||||
|
private final String DB_NAME = "dao_factory";
|
||||||
|
private final String COLLECTION_NAME = "customer";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerDAO createCustomerDAO() {
|
||||||
|
try {
|
||||||
|
MongoClient mongoClient = MongoClients.create(CONN_STR);
|
||||||
|
MongoDatabase database = mongoClient.getDatabase(DB_NAME);
|
||||||
|
MongoCollection<Document> customerCollection = database.getCollection(COLLECTION_NAME);
|
||||||
|
return new MongoCustomerDAO(customerCollection);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new RuntimeException("Error: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
FlatFileDataSourceFactory for flat file storage using JSON
|
||||||
|
``` java
|
||||||
|
public class FlatFileDataSourceFactory extends DAOFactory {
|
||||||
|
private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json";
|
||||||
|
@Override
|
||||||
|
public CustomerDAO createCustomerDAO() {
|
||||||
|
Path filePath = Paths.get(FILE_PATH);
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.serializeNulls()
|
||||||
|
.create();
|
||||||
|
return new FlatFileCustomerDAO(filePath, gson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, in the main function of client code, we will demonstrate CRUD operations on the Customer using three data source type.
|
||||||
|
``` java
|
||||||
|
// Perform CRUD H2 Database
|
||||||
|
LOGGER.debug("H2 - Create customer");
|
||||||
|
performCreateCustomer(customerDAO,
|
||||||
|
List.of(customerInmemory1, customerInmemory2, customerInmemory3));
|
||||||
|
LOGGER.debug("H2 - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateInmemory);
|
||||||
|
LOGGER.debug("H2 - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, 3L);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
|
||||||
|
// Perform CRUD MongoDb
|
||||||
|
daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo);
|
||||||
|
customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
LOGGER.debug("Mongo - Create customer");
|
||||||
|
performCreateCustomer(customerDAO, List.of(customer4, customer5));
|
||||||
|
LOGGER.debug("Mongo - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateMongo);
|
||||||
|
LOGGER.debug("Mongo - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, idCustomerMongo2);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
|
||||||
|
// Perform CRUD Flat file
|
||||||
|
daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile);
|
||||||
|
customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
LOGGER.debug("Flat file - Create customer");
|
||||||
|
performCreateCustomer(customerDAO,
|
||||||
|
List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3));
|
||||||
|
LOGGER.debug("Flat file - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateFlatFile);
|
||||||
|
LOGGER.debug("Flat file - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, 3L);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
```
|
||||||
|
|
||||||
|
The program output
|
||||||
|
``` java
|
||||||
|
17:17:24.368 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Create customer
|
||||||
|
17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Green)
|
||||||
|
17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
|
||||||
|
17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue)
|
||||||
|
17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Update customer
|
||||||
|
17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow)
|
||||||
|
17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
|
||||||
|
17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue)
|
||||||
|
17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Delete customer
|
||||||
|
17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow)
|
||||||
|
17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
|
||||||
|
17:17:24.747 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Create customer
|
||||||
|
17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
|
||||||
|
17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Elliot)
|
||||||
|
17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Update customer
|
||||||
|
17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
|
||||||
|
17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Henry)
|
||||||
|
17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Delete customer
|
||||||
|
17:17:24.850 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
|
||||||
|
17:17:24.876 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Create customer
|
||||||
|
17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Duc)
|
||||||
|
17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
|
||||||
|
17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat)
|
||||||
|
17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Update customer
|
||||||
|
17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh)
|
||||||
|
17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
|
||||||
|
17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat)
|
||||||
|
17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Delete customer
|
||||||
|
17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh)
|
||||||
|
17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
|
||||||
|
```
|
||||||
|
## When to Use the Data Access Object Factory Pattern in Java
|
||||||
|
|
||||||
|
Use the DAO Factory Pattern when:
|
||||||
|
|
||||||
|
* The application needs to support multiple types of storage (RDBMS, NoSQL, file system, etc.) with minimal changes to business logic.
|
||||||
|
* You want to abstract and isolate persistence logic from the core application logic.
|
||||||
|
* You aim to make your data access layer pluggable and easy to extend with new storage technologies.
|
||||||
|
* You want to enable easier unit testing and dependency injection by providing mock implementations of DAOs.
|
||||||
|
* Runtime configuration (e.g., via environment variables or application settings) determines which data source to use.
|
||||||
|
|
||||||
|
## Data Access Object Factory Pattern Java Tutorials
|
||||||
|
|
||||||
|
* [Core J2EE Patterns - Data Access Object (Oracle)](https://www.oracle.com/java/technologies/dataaccessobject.html)
|
||||||
|
* [DAO Factories: Java Design Patterns (Youtube)](https://www.youtube.com/watch?v=5HGe9s9qM-o)
|
||||||
|
* [Java Design Patterns and Architecture (CaveofProgramming)](https://caveofprogramming.teachable.com/courses/2084/lectures/39549)
|
||||||
|
|
||||||
|
## Real-World Applications of Data Access Object Factory Pattern in Java
|
||||||
|
|
||||||
|
* Enterprise Java Applications: Where switching between test, dev, and production databases is common (e.g., MySQL ↔ MongoDB ↔ In-Memory).
|
||||||
|
* Spring Data JPA & Repository Abstraction: Though Spring provides its own abstraction, the concept is similar to DAO factory for modular and pluggable persistence.
|
||||||
|
* Microservices with Varying Storage Backends: Different microservices might store data in SQL, NoSQL, or even flat files; using a DAO Factory per service ensures consistency.
|
||||||
|
* Data Integration Tools: Tools that support importing/exporting from various formats (CSV, JSON, databases) often use DAO factories behind the scenes.
|
||||||
|
* Framework-Level Implementations: Custom internal frameworks where persistence layers need to support multiple database types.
|
||||||
|
|
||||||
|
## Benefits and Trade-offs of Data Access Object Factory Pattern
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Abstraction of Data Source Logic: Client code interacts only with DAO interfaces, completely decoupled from how and where the data is stored.
|
||||||
|
* Flexibility in Persistence Strategy: Easily switch between databases (e.g., H2, MongoDB, flat files) by changing the factory configuration.
|
||||||
|
* Improved Maintainability: Storage logic for each data source is encapsulated within its own DAO implementation and factory, making it easier to update or extend.
|
||||||
|
* Code Reusability: Common data access logic (e.g., CRUD operations) can be reused across different implementations and projects.
|
||||||
|
* Testability: DAOs and factories can be mocked or stubbed easily, which supports unit testing and dependency injection.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
* Increased Complexity: Introducing abstract DAOs and multiple factory classes adds structural complexity to the codebase.
|
||||||
|
* Boilerplate Code: Requires defining many interfaces and implementations, even for simple data access needs.
|
||||||
|
* Less Transparent Behavior: Since clients access DAOs indirectly via factories, understanding the concrete data source behavior may require deeper inspection.
|
||||||
|
|
||||||
|
## Related Java Design Patterns
|
||||||
|
|
||||||
|
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): DAO Factory is a concrete application of the Factory Pattern, used to create DAO objects in a flexible way.
|
||||||
|
* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): When supporting multiple data sources (e.g., MySQLDAO, OracleDAO), DAO Factory can act as an Abstract Factory.
|
||||||
|
* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/data-access-object/): The core pattern managed by DAO Factory, it separates data access logic from business logic.
|
||||||
|
* [Singleton](https://java-design-patterns.com/patterns/singleton/): DAO Factory is often implemented as a Singleton to ensure only one instance manages DAO creation.
|
||||||
|
* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Can be used alongside DAO Factory to retrieve DAO services efficiently.
|
||||||
|
* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): In frameworks like Spring, DAOs are typically injected into the service layer instead of being retrieved from a factory.
|
||||||
|
|
||||||
|
|
||||||
|
## References and Credits
|
||||||
|
|
||||||
|
* [DAO Factory - J2EE Design Patterns Book](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re15.html)
|
||||||
|
* [DAO Factory patterns with Hibernate](http://www.giuseppeurso.eu/en/dao-factory-patterns-with-hibernate/)
|
||||||
|
* [Design Patterns - Java Means DURGA SOFT](https://www.scribd.com/document/407219980/2-DAO-Factory-Design-Pattern)
|
||||||
|
* [Generic DAO pattern - Hibernate](https://in.relation.to/2005/09/09/generic-dao-pattern-with-jdk-50/)
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@@ -0,0 +1,74 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.daofactory {
|
||||||
|
class App {
|
||||||
|
{static} void main(String[] args)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Customer<T> {
|
||||||
|
T id
|
||||||
|
String name
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomerDAO<ID> {
|
||||||
|
void save(Customer<ID> customer)
|
||||||
|
void update(Customer<ID> customer)
|
||||||
|
void delete(ID id)
|
||||||
|
List<Customer<ID>> findAll()
|
||||||
|
Optional<Customer<ID>> findById(ID id)
|
||||||
|
void deleteSchema()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DAOFactory {
|
||||||
|
{static} DAOFactory getDataSource(DataSourceType dataType)
|
||||||
|
{abstract} CustomerDAO createCustomerDAO()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DataSourceType {
|
||||||
|
H2
|
||||||
|
Mongo
|
||||||
|
FlatFile
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlatFileCustomerDAO implements CustomerDAO<Long> {
|
||||||
|
void save(Customer<Long> customer)
|
||||||
|
void update(Customer<Long> customer)
|
||||||
|
void delete(Long id)
|
||||||
|
List<Customer<Long>> findAll()
|
||||||
|
Optional<Customer<Long>> findById(Long id)
|
||||||
|
void deleteSchema()
|
||||||
|
}
|
||||||
|
|
||||||
|
class H2CustomerDAO implements CustomerDAO<Long> {
|
||||||
|
void save(Customer<Long> customer)
|
||||||
|
void update(Customer<Long> customer)
|
||||||
|
void delete(Long id)
|
||||||
|
List<Customer<Long>> findAll()
|
||||||
|
Optional<Customer<Long>> findById(Long id)
|
||||||
|
void deleteSchema()
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlatFileDataSourceFactory extends DAOFactory {
|
||||||
|
CustomerDAO createCustomerDAO()
|
||||||
|
}
|
||||||
|
|
||||||
|
class H2DataSourceFactory extends DAOFactory {
|
||||||
|
CustomerDAO createCustomerDAO()
|
||||||
|
}
|
||||||
|
|
||||||
|
class MongoCustomerDAO implements CustomerDAO<ObjectId> {
|
||||||
|
void save(Customer<ObjectId> customer)
|
||||||
|
void update(Customer<ObjectId> customer)
|
||||||
|
void delete(ObjectId id)
|
||||||
|
List<Customer<ObjectId>> findAll()
|
||||||
|
Optional<Customer<ObjectId>> findById(ObjectId id)
|
||||||
|
void deleteSchema()
|
||||||
|
}
|
||||||
|
class MongoDataSourceFactory extends DAOFactory {
|
||||||
|
CustomerDAO createCustomerDAO()
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSourceType ..+ DAOFactory
|
||||||
|
DAOFactory ..+ App
|
||||||
|
App --> Customer
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>dao-factory</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongodb-driver-legacy</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
|
||||||
|
CustomerDAO customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
|
||||||
|
// Perform CRUD H2 Database
|
||||||
|
if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) {
|
||||||
|
h2CustomerDAO.deleteSchema();
|
||||||
|
h2CustomerDAO.createSchema();
|
||||||
|
}
|
||||||
|
Customer<Long> customerInmemory1 = new Customer<>(1L, "Green");
|
||||||
|
Customer<Long> customerInmemory2 = new Customer<>(2L, "Red");
|
||||||
|
Customer<Long> customerInmemory3 = new Customer<>(3L, "Blue");
|
||||||
|
Customer<Long> customerUpdateInmemory = new Customer<>(1L, "Yellow");
|
||||||
|
|
||||||
|
LOGGER.debug("H2 - Create customer");
|
||||||
|
performCreateCustomer(
|
||||||
|
customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3));
|
||||||
|
LOGGER.debug("H2 - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateInmemory);
|
||||||
|
LOGGER.debug("H2 - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, 3L);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
|
||||||
|
// Perform CRUD MongoDb
|
||||||
|
daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
|
||||||
|
customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
ObjectId idCustomerMongo1 = new ObjectId();
|
||||||
|
ObjectId idCustomerMongo2 = new ObjectId();
|
||||||
|
Customer<ObjectId> customer4 = new Customer<>(idCustomerMongo1, "Masca");
|
||||||
|
Customer<ObjectId> customer5 = new Customer<>(idCustomerMongo2, "Elliot");
|
||||||
|
Customer<ObjectId> customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry");
|
||||||
|
|
||||||
|
LOGGER.debug("Mongo - Create customer");
|
||||||
|
performCreateCustomer(customerDAO, List.of(customer4, customer5));
|
||||||
|
LOGGER.debug("Mongo - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateMongo);
|
||||||
|
LOGGER.debug("Mongo - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, idCustomerMongo2);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
|
||||||
|
// Perform CRUD Flat file
|
||||||
|
daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
|
||||||
|
customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
Customer<Long> customerFlatFile1 = new Customer<>(1L, "Duc");
|
||||||
|
Customer<Long> customerFlatFile2 = new Customer<>(2L, "Quang");
|
||||||
|
Customer<Long> customerFlatFile3 = new Customer<>(3L, "Nhat");
|
||||||
|
Customer<Long> customerUpdateFlatFile = new Customer<>(1L, "Thanh");
|
||||||
|
LOGGER.debug("Flat file - Create customer");
|
||||||
|
performCreateCustomer(
|
||||||
|
customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3));
|
||||||
|
LOGGER.debug("Flat file - Update customer");
|
||||||
|
performUpdateCustomer(customerDAO, customerUpdateFlatFile);
|
||||||
|
LOGGER.debug("Flat file - Delete customer");
|
||||||
|
performDeleteCustomer(customerDAO, 3L);
|
||||||
|
deleteSchema(customerDAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteSchema(CustomerDAO customerDAO) {
|
||||||
|
customerDAO.deleteSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Serializable> void performCreateCustomer(
|
||||||
|
CustomerDAO<T> customerDAO, List<Customer<T>> customerList) {
|
||||||
|
for (Customer<T> customer : customerList) {
|
||||||
|
customerDAO.save(customer);
|
||||||
|
}
|
||||||
|
List<Customer<T>> customers = customerDAO.findAll();
|
||||||
|
for (Customer<T> customer : customers) {
|
||||||
|
LOGGER.debug(customer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Serializable> void performUpdateCustomer(
|
||||||
|
CustomerDAO<T> customerDAO, Customer<T> customerUpdate) {
|
||||||
|
customerDAO.update(customerUpdate);
|
||||||
|
List<Customer<T>> customers = customerDAO.findAll();
|
||||||
|
for (Customer<T> customer : customers) {
|
||||||
|
LOGGER.debug(customer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Serializable> void performDeleteCustomer(
|
||||||
|
CustomerDAO<T> customerDAO, T customerId) {
|
||||||
|
customerDAO.delete(customerId);
|
||||||
|
List<Customer<T>> customers = customerDAO.findAll();
|
||||||
|
for (Customer<T> customer : customers) {
|
||||||
|
LOGGER.debug(customer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
/** Customer exception */
|
||||||
|
public class CustomException extends RuntimeException {
|
||||||
|
public CustomException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A customer generic POJO that represents the data that can be stored in any supported data source.
|
||||||
|
* This class is designed t work with various ID types (e.g., Long, String, or ObjectId) through
|
||||||
|
* generic, making it adaptable to different persistence system.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class Customer<T extends Serializable> implements Serializable {
|
||||||
|
private T id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Data Access Object (DAO) pattern provides an abstraction layer between the application and
|
||||||
|
* the database. It encapsulates data access logic, allowing the application to work with domain
|
||||||
|
* objects instead of direct database operations.
|
||||||
|
*
|
||||||
|
* <p>Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping
|
||||||
|
* client code unchanged.
|
||||||
|
*
|
||||||
|
* @see H2CustomerDAO
|
||||||
|
* @see MongoCustomerDAO
|
||||||
|
* @see FlatFileCustomerDAO
|
||||||
|
*/
|
||||||
|
public interface CustomerDAO<T extends Serializable> {
|
||||||
|
/**
|
||||||
|
* Persist the given customer
|
||||||
|
*
|
||||||
|
* @param customer the customer to persist
|
||||||
|
*/
|
||||||
|
void save(Customer<T> customer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the given customer
|
||||||
|
*
|
||||||
|
* @param customer the customer to update
|
||||||
|
*/
|
||||||
|
void update(Customer<T> customer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the customer with the given id
|
||||||
|
*
|
||||||
|
* @param id the id of the customer to delete
|
||||||
|
*/
|
||||||
|
void delete(T id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all customers
|
||||||
|
*
|
||||||
|
* @return a list of customers
|
||||||
|
*/
|
||||||
|
List<Customer<T>> findAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the customer with the given id
|
||||||
|
*
|
||||||
|
* @param id the id of the customer to find
|
||||||
|
* @return the customer with the given id
|
||||||
|
*/
|
||||||
|
Optional<Customer<T>> findById(T id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the customer schema. After executing the statements, this function will be called to
|
||||||
|
* clean up the data and delete the records.
|
||||||
|
*/
|
||||||
|
void deleteSchema();
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract factory class that provides a way to create concrete DAO (Data Access Object)
|
||||||
|
* factories for different data sources types (e.g., H2, Mongo, FlatFile).
|
||||||
|
*
|
||||||
|
* <p>This class follows the Abstract Factory design pattern, allowing applications to retrieve the
|
||||||
|
* approriate DAO implementation without being tightly coupled to a specific data source.
|
||||||
|
*
|
||||||
|
* @see H2DataSourceFactory
|
||||||
|
* @see MongoDataSourceFactory
|
||||||
|
* @see FlatFileDataSourceFactory
|
||||||
|
*/
|
||||||
|
public abstract class DAOFactory {
|
||||||
|
/**
|
||||||
|
* Retrieves a {@link CustomerDAO} implementation specific to the underlying data source..
|
||||||
|
*
|
||||||
|
* @return A data source-specific implementation of {@link CustomerDAO}
|
||||||
|
*/
|
||||||
|
public abstract CustomerDAO createCustomerDAO();
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code DAOFactoryProvider} is a utility class responsible for providing concrete implementations
|
||||||
|
* of the {@link DAOFactory} interface based on the specified data source type.
|
||||||
|
*
|
||||||
|
* <p>This class acts as an entry point to obtain DAO factories for different storage mechanisms
|
||||||
|
* such as relational databases (e.g., H2), document stores (e.g., MongoDB), or file-based systems.
|
||||||
|
* It uses the {@link DataSourceType} enumeration to determine which concrete factory to
|
||||||
|
* instantiate.
|
||||||
|
*
|
||||||
|
* <p>Example usage:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public class DAOFactoryProvider {
|
||||||
|
|
||||||
|
private DAOFactoryProvider() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a concrete {@link DAOFactory} intance based on the specified data source type.
|
||||||
|
*
|
||||||
|
* @param dataSourceType The type of data source for which a factory is needed. Supported values:
|
||||||
|
* {@code H2}, {@code Mongo}, {@code FlatFile}
|
||||||
|
* @return A {@link DAOFactory} implementation corresponding to the given data source type.
|
||||||
|
* @throws IllegalArgumentException if the given data source type is not supported.
|
||||||
|
*/
|
||||||
|
public static DAOFactory getDataSource(DataSourceType dataSourceType) {
|
||||||
|
return switch (dataSourceType) {
|
||||||
|
case H2 -> new H2DataSourceFactory();
|
||||||
|
case MONGO -> new MongoDataSourceFactory();
|
||||||
|
case FLAT_FILE -> new FlatFileDataSourceFactory();
|
||||||
|
default -> throw new IllegalArgumentException("Unsupported data source type");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
/** Enumerates the types of data sources supported by the application. */
|
||||||
|
public enum DataSourceType {
|
||||||
|
H2,
|
||||||
|
MONGO,
|
||||||
|
FLAT_FILE
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Flat File implementation of {@link CustomerDAO}, which store the customer data in a JSON file
|
||||||
|
* at path {@code ~/Desktop/customer.json}.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FlatFileCustomerDAO implements CustomerDAO<Long> {
|
||||||
|
private final Path filePath;
|
||||||
|
private final Gson gson;
|
||||||
|
Type customerListType = new TypeToken<List<Customer<Long>>>() {}.getType();
|
||||||
|
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
return new FileReader(filePath.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
return new FileWriter(filePath.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void save(Customer<Long> customer) {
|
||||||
|
List<Customer<Long>> customers = new LinkedList<>();
|
||||||
|
if (filePath.toFile().exists()) {
|
||||||
|
try (Reader reader = createReader(filePath)) {
|
||||||
|
customers = gson.fromJson(reader, customerListType);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to read customer data", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customers.add(customer);
|
||||||
|
try (Writer writer = createWriter(filePath)) {
|
||||||
|
gson.toJson(customers, writer);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to write customer data", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void update(Customer<Long> customer) {
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
throw new CustomException("File not found");
|
||||||
|
}
|
||||||
|
List<Customer<Long>> customers;
|
||||||
|
try (Reader reader = createReader(filePath)) {
|
||||||
|
customers = gson.fromJson(reader, customerListType);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to read customer data", ex);
|
||||||
|
}
|
||||||
|
customers.stream()
|
||||||
|
.filter(c -> c.getId().equals(customer.getId()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresentOrElse(
|
||||||
|
c -> c.setName(customer.getName()),
|
||||||
|
() -> {
|
||||||
|
throw new CustomException("Customer not found with id: " + customer.getId());
|
||||||
|
});
|
||||||
|
try (Writer writer = createWriter(filePath)) {
|
||||||
|
gson.toJson(customers, writer);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to write customer data", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void delete(Long id) {
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
throw new CustomException("File not found");
|
||||||
|
}
|
||||||
|
List<Customer<Long>> customers;
|
||||||
|
try (Reader reader = createReader(filePath)) {
|
||||||
|
customers = gson.fromJson(reader, customerListType);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to read customer data", ex);
|
||||||
|
}
|
||||||
|
Customer<Long> customerToRemove =
|
||||||
|
customers.stream()
|
||||||
|
.filter(c -> c.getId().equals(id))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new CustomException("Customer not found with id: " + id));
|
||||||
|
customers.remove(customerToRemove);
|
||||||
|
try (Writer writer = createWriter(filePath)) {
|
||||||
|
gson.toJson(customers, writer);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to write customer data", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public List<Customer<Long>> findAll() {
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
throw new CustomException("File not found");
|
||||||
|
}
|
||||||
|
List<Customer<Long>> customers;
|
||||||
|
try (Reader reader = createReader(filePath)) {
|
||||||
|
customers = gson.fromJson(reader, customerListType);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to read customer data", ex);
|
||||||
|
}
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Optional<Customer<Long>> findById(Long id) {
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
throw new CustomException("File not found");
|
||||||
|
}
|
||||||
|
List<Customer<Long>> customers = null;
|
||||||
|
try (Reader reader = createReader(filePath)) {
|
||||||
|
customers = gson.fromJson(reader, customerListType);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to read customer data", ex);
|
||||||
|
}
|
||||||
|
return customers.stream().filter(c -> c.getId().equals(id)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void deleteSchema() {
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
throw new CustomException("File not found");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Files.delete(filePath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CustomException("Failed to delete customer data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/** FlatFileDataSourceFactory concrete factory. */
|
||||||
|
public class FlatFileDataSourceFactory extends DAOFactory {
|
||||||
|
private static final String FILE_PATH =
|
||||||
|
System.getProperty("user.home") + "/Desktop/customer.json";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerDAO<Long> createCustomerDAO() {
|
||||||
|
Path filePath = Paths.get(FILE_PATH);
|
||||||
|
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
|
||||||
|
return new FlatFileCustomerDAO(filePath, gson);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of {@link CustomerDAO} that uses H2 database (http://www.h2database.com/) which
|
||||||
|
* is an in-memory database and data will lost after application exits.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class H2CustomerDAO implements CustomerDAO<Long> {
|
||||||
|
private final DataSource dataSource;
|
||||||
|
private static final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)";
|
||||||
|
private static final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?";
|
||||||
|
private static final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?";
|
||||||
|
private static final String SELECT_CUSTOMER_BY_ID =
|
||||||
|
"SELECT customer.id, customer.name FROM customer WHERE id= ?";
|
||||||
|
private static final String SELECT_ALL_CUSTOMERS = "SELECT customer.* FROM customer";
|
||||||
|
private static final String CREATE_SCHEMA =
|
||||||
|
"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
|
||||||
|
private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void save(Customer<Long> customer) {
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) {
|
||||||
|
saveStatement.setLong(1, customer.getId());
|
||||||
|
saveStatement.setString(2, customer.getName());
|
||||||
|
saveStatement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void update(Customer<Long> customer) {
|
||||||
|
if (Objects.isNull(customer) || Objects.isNull(customer.getId())) {
|
||||||
|
throw new CustomException("Custome null or customer id null");
|
||||||
|
}
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
|
||||||
|
PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) {
|
||||||
|
selectStatement.setLong(1, customer.getId());
|
||||||
|
try (ResultSet resultSet = selectStatement.executeQuery()) {
|
||||||
|
if (!resultSet.next()) {
|
||||||
|
throw new CustomException("Customer not found with id: " + customer.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateStatement.setString(1, customer.getName());
|
||||||
|
updateStatement.setLong(2, customer.getId());
|
||||||
|
updateStatement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void delete(Long id) {
|
||||||
|
if (Objects.isNull(id)) {
|
||||||
|
throw new CustomException("Customer id null");
|
||||||
|
}
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
|
||||||
|
PreparedStatement deleteStatement = connection.prepareStatement(DELETE_CUSTOMER)) {
|
||||||
|
selectStatement.setLong(1, id);
|
||||||
|
try (ResultSet resultSet = selectStatement.executeQuery()) {
|
||||||
|
if (!resultSet.next()) {
|
||||||
|
throw new CustomException("Customer not found with id: " + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteStatement.setLong(1, id);
|
||||||
|
deleteStatement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public List<Customer<Long>> findAll() {
|
||||||
|
List<Customer<Long>> customers = new LinkedList<>();
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) {
|
||||||
|
try (ResultSet resultSet = selectStatement.executeQuery()) {
|
||||||
|
while (resultSet.next()) {
|
||||||
|
Long idCustomer = resultSet.getLong("id");
|
||||||
|
String nameCustomer = resultSet.getString("name");
|
||||||
|
customers.add(new Customer<>(idCustomer, nameCustomer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Optional<Customer<Long>> findById(Long id) {
|
||||||
|
if (Objects.isNull(id)) {
|
||||||
|
throw new CustomException("Customer id null");
|
||||||
|
}
|
||||||
|
Customer<Long> customer = null;
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement selectByIdStatement =
|
||||||
|
connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) {
|
||||||
|
selectByIdStatement.setLong(1, id);
|
||||||
|
try (ResultSet resultSet = selectByIdStatement.executeQuery()) {
|
||||||
|
while (resultSet.next()) {
|
||||||
|
Long idCustomer = resultSet.getLong("id");
|
||||||
|
String nameCustomer = resultSet.getString("name");
|
||||||
|
customer = new Customer<>(idCustomer, nameCustomer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create customer schema. */
|
||||||
|
public void createSchema() {
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
Statement statement = connection.createStatement()) {
|
||||||
|
statement.execute(CREATE_SCHEMA);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc}} */
|
||||||
|
@Override
|
||||||
|
public void deleteSchema() {
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
Statement statement = connection.createStatement(); ) {
|
||||||
|
statement.execute(DROP_SCHEMA);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new CustomException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import org.h2.jdbcx.JdbcDataSource;
|
||||||
|
|
||||||
|
/** H2DataSourceFactory concrete factory. */
|
||||||
|
public class H2DataSourceFactory extends DAOFactory {
|
||||||
|
private static final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
|
||||||
|
private static final String USER = "sa";
|
||||||
|
private static final String PASS = "";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerDAO createCustomerDAO() {
|
||||||
|
return new H2CustomerDAO(createDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource createDataSource() {
|
||||||
|
var dataSource = new JdbcDataSource();
|
||||||
|
dataSource.setURL(DB_URL);
|
||||||
|
dataSource.setUser(USER);
|
||||||
|
dataSource.setPassword(PASS);
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import com.mongodb.client.FindIterable;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.Updates;
|
||||||
|
import com.mongodb.client.result.DeleteResult;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
|
/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MongoCustomerDAO implements CustomerDAO<ObjectId> {
|
||||||
|
private final MongoCollection<Document> customerCollection;
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void save(Customer<ObjectId> customer) {
|
||||||
|
Document customerDocument = new Document("_id", customer.getId());
|
||||||
|
customerDocument.append("name", customer.getName());
|
||||||
|
customerCollection.insertOne(customerDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void update(Customer<ObjectId> customer) {
|
||||||
|
Document updateQuery = new Document("_id", customer.getId());
|
||||||
|
Bson update = Updates.set("name", customer.getName());
|
||||||
|
customerCollection.updateOne(updateQuery, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void delete(ObjectId objectId) {
|
||||||
|
Bson deleteQuery = Filters.eq("_id", objectId);
|
||||||
|
DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery);
|
||||||
|
if (deleteResult.getDeletedCount() == 0) {
|
||||||
|
throw new CustomException("Delete failed: No document found with id: " + objectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public List<Customer<ObjectId>> findAll() {
|
||||||
|
List<Customer<ObjectId>> customers = new LinkedList<>();
|
||||||
|
FindIterable<Document> customerDocuments = customerCollection.find();
|
||||||
|
for (Document customerDocument : customerDocuments) {
|
||||||
|
Customer<ObjectId> customer =
|
||||||
|
new Customer<>(
|
||||||
|
(ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
|
||||||
|
customers.add(customer);
|
||||||
|
}
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Optional<Customer<ObjectId>> findById(ObjectId objectId) {
|
||||||
|
Bson filter = Filters.eq("_id", objectId);
|
||||||
|
Document customerDocument = customerCollection.find(filter).first();
|
||||||
|
Customer<ObjectId> customerResult = null;
|
||||||
|
if (customerDocument != null) {
|
||||||
|
customerResult =
|
||||||
|
new Customer<>(
|
||||||
|
(ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(customerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void deleteSchema() {
|
||||||
|
customerCollection.drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
|
/** MongoDataSourceFactory concrete factory. */
|
||||||
|
public class MongoDataSourceFactory extends DAOFactory {
|
||||||
|
private static final String CONN_STR = "mongodb://localhost:27017/";
|
||||||
|
private static final String DB_NAME = "dao_factory";
|
||||||
|
private static final String COLLECTION_NAME = "customer";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerDAO<ObjectId> createCustomerDAO() {
|
||||||
|
try {
|
||||||
|
MongoClient mongoClient = MongoClients.create(CONN_STR);
|
||||||
|
MongoDatabase database = mongoClient.getDatabase(DB_NAME);
|
||||||
|
MongoCollection<Document> customerCollection = database.getCollection(COLLECTION_NAME);
|
||||||
|
return new MongoCustomerDAO(customerCollection);
|
||||||
|
} catch (CustomException e) {
|
||||||
|
throw new CustomException("Error: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.iluwatar.daofactory" level="DEBUG"/>
|
||||||
|
<root level="ERROR">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** {@link App} */
|
||||||
|
class AppTest {
|
||||||
|
/** Test perform CRUD in main class */
|
||||||
|
private CustomerDAO<Long> mockLongCustomerDAO;
|
||||||
|
|
||||||
|
private CustomerDAO<ObjectId> mockObjectIdCustomerDAO;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
mockLongCustomerDAO = mock(CustomerDAO.class);
|
||||||
|
mockObjectIdCustomerDAO = mock(CustomerDAO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPerformCreateCustomerWithLongId() {
|
||||||
|
Customer<Long> c1 = new Customer<>(1L, "Test1");
|
||||||
|
Customer<Long> c2 = new Customer<>(2L, "Test2");
|
||||||
|
|
||||||
|
when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2));
|
||||||
|
|
||||||
|
App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2));
|
||||||
|
|
||||||
|
verify(mockLongCustomerDAO).save(c1);
|
||||||
|
verify(mockLongCustomerDAO).save(c2);
|
||||||
|
verify(mockLongCustomerDAO).findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPerformUpdateCustomerWithObjectId() {
|
||||||
|
ObjectId id = new ObjectId();
|
||||||
|
Customer<ObjectId> updatedCustomer = new Customer<>(id, "Updated");
|
||||||
|
|
||||||
|
when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer));
|
||||||
|
|
||||||
|
App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer);
|
||||||
|
|
||||||
|
verify(mockObjectIdCustomerDAO).update(updatedCustomer);
|
||||||
|
verify(mockObjectIdCustomerDAO).findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPerformDeleteCustomerWithLongId() {
|
||||||
|
Long id = 100L;
|
||||||
|
Customer<Long> remainingCustomer = new Customer<>(1L, "Remaining");
|
||||||
|
|
||||||
|
when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer));
|
||||||
|
|
||||||
|
App.performDeleteCustomer(mockLongCustomerDAO, id);
|
||||||
|
|
||||||
|
verify(mockLongCustomerDAO).delete(id);
|
||||||
|
verify(mockLongCustomerDAO).findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteSchema() {
|
||||||
|
App.deleteSchema(mockLongCustomerDAO);
|
||||||
|
verify(mockLongCustomerDAO).deleteSchema();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** {@link DAOFactory} */
|
||||||
|
class DAOFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyH2CustomerDAOCreation() {
|
||||||
|
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
|
||||||
|
var customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
assertInstanceOf(H2CustomerDAO.class, customerDAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyMongoCustomerDAOCreation() {
|
||||||
|
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
|
||||||
|
var customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
assertInstanceOf(MongoCustomerDAO.class, customerDAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyFlatFileCustomerDAOCreation() {
|
||||||
|
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
|
||||||
|
var customerDAO = daoFactory.createCustomerDAO();
|
||||||
|
assertInstanceOf(FlatFileCustomerDAO.class, customerDAO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,500 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
/** {@link FlatFileCustomerDAO} */
|
||||||
|
class FlatFileCustomerDAOTest {
|
||||||
|
private Path filePath;
|
||||||
|
private File file;
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
|
private final Type customerListType = new TypeToken<List<Customer<Long>>>() {}.getType();
|
||||||
|
private final Customer<Long> existingCustomer = new Customer<>(1L, "Thanh");
|
||||||
|
private FlatFileCustomerDAO flatFileCustomerDAO;
|
||||||
|
private FileReader fileReader;
|
||||||
|
private FileWriter fileWriter;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
filePath = mock(Path.class);
|
||||||
|
file = mock(File.class);
|
||||||
|
gson = mock(Gson.class);
|
||||||
|
fileReader = mock(FileReader.class);
|
||||||
|
fileWriter = mock(FileWriter.class);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
when(filePath.toFile()).thenReturn(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario Save Customer */
|
||||||
|
@Nested
|
||||||
|
class Save {
|
||||||
|
@Test
|
||||||
|
void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
flatFileCustomerDAO.save(existingCustomer);
|
||||||
|
|
||||||
|
verify(gson)
|
||||||
|
.toJson(
|
||||||
|
argThat(
|
||||||
|
(List<Customer<Long>> list) ->
|
||||||
|
list.size() == 1 && list.getFirst().equals(existingCustomer)),
|
||||||
|
eq(fileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
|
||||||
|
flatFileCustomerDAO.save(existingCustomer);
|
||||||
|
|
||||||
|
verify(gson).fromJson(fileReader, customerListType);
|
||||||
|
verify(gson)
|
||||||
|
.toJson(
|
||||||
|
argThat(
|
||||||
|
(List<Customer<Long>> list) ->
|
||||||
|
list.size() == 1 && list.getFirst().equals(existingCustomer)),
|
||||||
|
eq(fileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() {
|
||||||
|
List<Customer<Long>> customers = new LinkedList<>();
|
||||||
|
customers.add(new Customer<>(2L, "Duc"));
|
||||||
|
customers.add(new Customer<>(3L, "Nguyen"));
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(customers);
|
||||||
|
|
||||||
|
flatFileCustomerDAO.save(existingCustomer);
|
||||||
|
|
||||||
|
verify(gson).fromJson(fileReader, customerListType);
|
||||||
|
verify(gson).toJson(argThat((List<Customer<Long>> list) -> list.size() == 3), eq(fileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenReadFails_thenThrowException() {
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenWriteFails_thenThrowException() {
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to write file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario Update Customer */
|
||||||
|
@Nested
|
||||||
|
class Update {
|
||||||
|
@Test
|
||||||
|
void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenReadFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenWriteFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType)))
|
||||||
|
.thenReturn(
|
||||||
|
new LinkedList<>() {
|
||||||
|
{
|
||||||
|
add(new Customer<>(1L, "Quang"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to write file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
flatFileCustomerDAO.update(existingCustomer);
|
||||||
|
verify(gson)
|
||||||
|
.toJson(
|
||||||
|
argThat(
|
||||||
|
(List<Customer<Long>> customers) ->
|
||||||
|
customers.size() == 1
|
||||||
|
&& customers.stream()
|
||||||
|
.anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh"))),
|
||||||
|
eq(fileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(2L, "Quang"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario Delete Customer */
|
||||||
|
@Nested
|
||||||
|
class Delete {
|
||||||
|
@Test
|
||||||
|
void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenReadFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenWriteFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to write file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidId_whenDeleteCustomer_thenDeleteSucceed() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
existingListCustomer.add(new Customer<>(2L, "Thanh"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
flatFileCustomerDAO.delete(1L);
|
||||||
|
assertEquals(1, existingListCustomer.size());
|
||||||
|
verify(gson)
|
||||||
|
.toJson(
|
||||||
|
argThat(
|
||||||
|
(List<Customer<Long>> customers) ->
|
||||||
|
customers.stream()
|
||||||
|
.noneMatch(c -> c.getId().equals(1L) && c.getName().equals("Quang"))),
|
||||||
|
eq(fileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdNotExist_whenDeleteCustomer_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
existingListCustomer.add(new Customer<>(2L, "Thanh"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) {
|
||||||
|
return fileReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(3L));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario Find All Customer */
|
||||||
|
@Nested
|
||||||
|
class FindAll {
|
||||||
|
@Test
|
||||||
|
void givenFileNotExist_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenReadFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenEmptyCustomer_thenReturnEmptyList() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
|
||||||
|
List<Customer<Long>> customers = flatFileCustomerDAO.findAll();
|
||||||
|
assertEquals(0, customers.size());
|
||||||
|
verify(gson).fromJson(fileReader, customerListType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenCustomerExist_thenReturnCustomerList() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
existingListCustomer.add(new Customer<>(2L, "Thanh"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
List<Customer<Long>> customers = flatFileCustomerDAO.findAll();
|
||||||
|
assertEquals(2, customers.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario Find By Id Customer */
|
||||||
|
@Nested
|
||||||
|
class FindById {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenFilePathNotExist_whenFindById_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenReadFails_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
flatFileCustomerDAO =
|
||||||
|
new FlatFileCustomerDAO(filePath, gson) {
|
||||||
|
@Override
|
||||||
|
protected Reader createReader(Path filePath) throws IOException {
|
||||||
|
throw new IOException("Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Writer createWriter(Path filePath) {
|
||||||
|
return fileWriter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerExist_whenFindById_thenReturnCustomer() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
List<Customer<Long>> existingListCustomer = new LinkedList<>();
|
||||||
|
existingListCustomer.add(new Customer<>(1L, "Quang"));
|
||||||
|
existingListCustomer.add(new Customer<>(2L, "Thanh"));
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
|
||||||
|
Optional<Customer<Long>> customer = flatFileCustomerDAO.findById(1L);
|
||||||
|
assertTrue(customer.isPresent());
|
||||||
|
assertEquals("Quang", customer.get().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
|
||||||
|
Optional<Customer<Long>> customers = flatFileCustomerDAO.findById(1L);
|
||||||
|
assertTrue(customers.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clas test with scenario Delete schema */
|
||||||
|
@Nested
|
||||||
|
class DeleteSchema {
|
||||||
|
@Test
|
||||||
|
void givenFilePathExist_thenDeleteFile() {
|
||||||
|
when(file.exists()).thenReturn(true);
|
||||||
|
|
||||||
|
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||||
|
flatFileCustomerDAO.deleteSchema();
|
||||||
|
mockedFiles.verify(() -> Files.delete(filePath), times(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenFilePathNotExist_thenThrowException() {
|
||||||
|
when(file.exists()).thenReturn(false);
|
||||||
|
|
||||||
|
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||||
|
assertThrows(CustomException.class, () -> flatFileCustomerDAO.deleteSchema());
|
||||||
|
mockedFiles.verify(() -> Files.delete(filePath), times(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import org.h2.jdbcx.JdbcDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
/** Tests {@link H2CustomerDAO} */
|
||||||
|
class H2CustomerDAOTest {
|
||||||
|
private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
|
||||||
|
private static final String USER = "sa";
|
||||||
|
private static final String PASS = "";
|
||||||
|
private static final String CREATE_SCHEMA =
|
||||||
|
"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
|
||||||
|
private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
|
||||||
|
private final Customer<Long> existingCustomer = new Customer<>(1L, "Nguyen");
|
||||||
|
private H2CustomerDAO h2CustomerDAO;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createSchema() throws SQLException {
|
||||||
|
try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
|
||||||
|
var statement = connection.createStatement()) {
|
||||||
|
statement.execute(CREATE_SCHEMA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void deleteSchema() throws SQLException {
|
||||||
|
try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
|
||||||
|
var statement = connection.createStatement()) {
|
||||||
|
statement.execute(DROP_SCHEMA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test for scenario connect with datasource succeed */
|
||||||
|
@Nested
|
||||||
|
class ConnectionSucceed {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
var dataSource = new JdbcDataSource();
|
||||||
|
dataSource.setURL(DB_URL);
|
||||||
|
dataSource.setUser(USER);
|
||||||
|
dataSource.setPassword(PASS);
|
||||||
|
h2CustomerDAO = new H2CustomerDAO(dataSource);
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer));
|
||||||
|
var customer = h2CustomerDAO.findById(existingCustomer.getId());
|
||||||
|
assertTrue(customer.isPresent());
|
||||||
|
assertEquals(customer.get().getName(), existingCustomer.getName());
|
||||||
|
assertEquals(customer.get().getId(), existingCustomer.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SaveCustomer {
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenSaveCustomer_thenAddSucceed() {
|
||||||
|
var customer = new Customer<>(2L, "Duc");
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.save(customer));
|
||||||
|
var customerInDb = h2CustomerDAO.findById(customer.getId());
|
||||||
|
assertTrue(customerInDb.isPresent());
|
||||||
|
assertEquals(customerInDb.get().getName(), customer.getName());
|
||||||
|
assertEquals(customerInDb.get().getId(), customer.getId());
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(2, customers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() {
|
||||||
|
var customer = new Customer<>(existingCustomer.getId(), "Duc");
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(1, customers.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class UpdateCustomer {
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
|
||||||
|
var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.update(customerUpdate));
|
||||||
|
var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
|
||||||
|
assertTrue(customerInDb.isPresent());
|
||||||
|
assertEquals(customerInDb.get().getName(), customerUpdate.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() {
|
||||||
|
var customerUpdate = new Customer<>(100L, "Duc");
|
||||||
|
var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
|
||||||
|
assertTrue(customerInDb.isEmpty());
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenNull_whenUpdateCustomer_thenThrowException() {
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.update(null));
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(1, customers.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeleteCustomer {
|
||||||
|
@Test
|
||||||
|
void givenValidId_whenDeleteCustomer_thenDeleteSucceed() {
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
|
||||||
|
var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
|
||||||
|
assertTrue(customerInDb.isEmpty());
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(0, customers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() {
|
||||||
|
var customerInDb = h2CustomerDAO.findById(100L);
|
||||||
|
assertTrue(customerInDb.isEmpty());
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(100L));
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(1, customers.size());
|
||||||
|
assertEquals(existingCustomer.getName(), customers.get(0).getName());
|
||||||
|
assertEquals(existingCustomer.getId(), customers.get(0).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenNull_whenDeleteCustomer_thenThrowException() {
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(null));
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(1, customers.size());
|
||||||
|
assertEquals(existingCustomer.getName(), customers.get(0).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindAllCustomers {
|
||||||
|
@Test
|
||||||
|
void givenNonCustomerInDb_whenFindAllCustomer_thenReturnEmptyList() {
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(0, customers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() {
|
||||||
|
List<Customer<Long>> customers = h2CustomerDAO.findAll();
|
||||||
|
assertEquals(1, customers.size());
|
||||||
|
assertEquals(existingCustomer.getName(), customers.get(0).getName());
|
||||||
|
assertEquals(existingCustomer.getId(), customers.get(0).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindCustomerById {
|
||||||
|
@Test
|
||||||
|
void givenValidId_whenFindById_thenReturnCustomer() {
|
||||||
|
var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
|
||||||
|
assertTrue(customerInDb.isPresent());
|
||||||
|
assertEquals(existingCustomer.getName(), customerInDb.get().getName());
|
||||||
|
assertEquals(existingCustomer.getId(), customerInDb.get().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() {
|
||||||
|
var customerNotExist = h2CustomerDAO.findById(100L);
|
||||||
|
assertTrue(customerNotExist.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenNull_whenFindById_thenThrowException() {
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.findById(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateSchema {
|
||||||
|
@Test
|
||||||
|
void whenCreateSchema_thenNotThrowException() {
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.createSchema());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeleteSchema {
|
||||||
|
@Test
|
||||||
|
void whenDeleteSchema_thenNotThrowException() {
|
||||||
|
assertDoesNotThrow(() -> h2CustomerDAO.deleteSchema());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class test with scenario connect with data source failed */
|
||||||
|
@Nested
|
||||||
|
class ConnectionFailed {
|
||||||
|
private static final String EXCEPTION_CAUSE = "Connection not available";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws SQLException {
|
||||||
|
h2CustomerDAO = new H2CustomerDAO(mockedDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource mockedDataSource() throws SQLException {
|
||||||
|
var mockedDataSource = mock(DataSource.class);
|
||||||
|
var mockedConnection = mock(Connection.class);
|
||||||
|
var exception = new SQLException(EXCEPTION_CAUSE);
|
||||||
|
doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
|
||||||
|
doThrow(exception).when(mockedConnection).createStatement();
|
||||||
|
doReturn(mockedConnection).when(mockedDataSource).getConnection();
|
||||||
|
return mockedDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenSaveCustomer_thenThrowException() {
|
||||||
|
var customer = new Customer<>(2L, "Duc");
|
||||||
|
CustomException exception =
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenUpdateCustomer_thenThrowException() {
|
||||||
|
var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
|
||||||
|
CustomException exception =
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidId_whenDeleteCustomer_thenThrowException() {
|
||||||
|
Long idCustomer = existingCustomer.getId();
|
||||||
|
CustomException exception =
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(idCustomer));
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenFindAll_thenThrowException() {
|
||||||
|
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::findAll);
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenFindById_thenThrowException() {
|
||||||
|
Long idCustomer = existingCustomer.getId();
|
||||||
|
CustomException exception =
|
||||||
|
assertThrows(CustomException.class, () -> h2CustomerDAO.findById(idCustomer));
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenCreateSchema_thenThrowException() {
|
||||||
|
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::createSchema);
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenDeleteSchema_thenThrowException() {
|
||||||
|
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::deleteSchema);
|
||||||
|
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.daofactory;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.mongodb.client.FindIterable;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoCursor;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.result.DeleteResult;
|
||||||
|
import com.mongodb.client.result.UpdateResult;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.bson.BsonDocument;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Tests {@link MongoCustomerDAO} */
|
||||||
|
class MongoCustomerDAOTest {
|
||||||
|
MongoCollection<Document> customerCollection = mock(MongoCollection.class);
|
||||||
|
MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() {
|
||||||
|
Customer<ObjectId> customer = new Customer<>(new ObjectId(), "John");
|
||||||
|
mongoCustomerDAO.save(customer);
|
||||||
|
verify(customerCollection)
|
||||||
|
.insertOne(
|
||||||
|
argThat(
|
||||||
|
document ->
|
||||||
|
document.get("_id").equals(customer.getId())
|
||||||
|
&& document.get("name").equals(customer.getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
|
||||||
|
ObjectId customerId = new ObjectId();
|
||||||
|
Customer<ObjectId> customerUpdated = new Customer<>(customerId, "John");
|
||||||
|
when(customerCollection.updateOne(any(Bson.class), any(Bson.class)))
|
||||||
|
.thenReturn(UpdateResult.acknowledged(1L, 1L, null));
|
||||||
|
mongoCustomerDAO.update(customerUpdated);
|
||||||
|
verify(customerCollection)
|
||||||
|
.updateOne(
|
||||||
|
argThat(
|
||||||
|
(Bson filter) -> {
|
||||||
|
Document filterDoc = (Document) filter;
|
||||||
|
return filterDoc.getObjectId("_id").equals(customerId);
|
||||||
|
}),
|
||||||
|
argThat(
|
||||||
|
(Bson update) -> {
|
||||||
|
BsonDocument bsonDoc = update.toBsonDocument();
|
||||||
|
BsonDocument setDoc = bsonDoc.getDocument("$set");
|
||||||
|
return setDoc.getString("name").getValue().equals(customerUpdated.getName());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() {
|
||||||
|
ObjectId customerId = new ObjectId();
|
||||||
|
when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(1));
|
||||||
|
mongoCustomerDAO.delete(customerId);
|
||||||
|
verify(customerCollection)
|
||||||
|
.deleteOne(
|
||||||
|
argThat(
|
||||||
|
(Bson filter) -> {
|
||||||
|
BsonDocument filterDoc = filter.toBsonDocument();
|
||||||
|
return filterDoc.getObjectId("_id").getValue().equals(customerId);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenIdNotExist_whenDeleteCustomer_thenThrowException() {
|
||||||
|
ObjectId customerId = new ObjectId();
|
||||||
|
when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0));
|
||||||
|
assertThrows(CustomException.class, () -> mongoCustomerDAO.delete(customerId));
|
||||||
|
verify(customerCollection)
|
||||||
|
.deleteOne(
|
||||||
|
argThat(
|
||||||
|
(Bson filter) -> {
|
||||||
|
BsonDocument filterDoc = filter.toBsonDocument();
|
||||||
|
return filterDoc.getObjectId("_id").getValue().equals(customerId);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findAll_thenReturnAllCustomers() {
|
||||||
|
FindIterable<Document> findIterable = mock(FindIterable.class);
|
||||||
|
MongoCursor<Document> cursor = mock(MongoCursor.class);
|
||||||
|
Document customerDoc1 = new Document("_id", new ObjectId()).append("name", "Duc");
|
||||||
|
Document customerDoc2 = new Document("_id", new ObjectId()).append("name", "Thanh");
|
||||||
|
when(customerCollection.find()).thenReturn(findIterable);
|
||||||
|
when(findIterable.iterator()).thenReturn(cursor);
|
||||||
|
when(cursor.hasNext()).thenReturn(true, true, false);
|
||||||
|
when(cursor.next()).thenReturn(customerDoc1, customerDoc2);
|
||||||
|
List<Customer<ObjectId>> customerList = mongoCustomerDAO.findAll();
|
||||||
|
assertEquals(2, customerList.size());
|
||||||
|
verify(customerCollection).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidId_whenFindById_thenReturnCustomer() {
|
||||||
|
FindIterable<Document> findIterable = mock(FindIterable.class);
|
||||||
|
ObjectId customerId = new ObjectId();
|
||||||
|
String customerName = "Duc";
|
||||||
|
Document customerDoc = new Document("_id", customerId).append("name", customerName);
|
||||||
|
when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
|
||||||
|
when(findIterable.first()).thenReturn(customerDoc);
|
||||||
|
|
||||||
|
Optional<Customer<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
|
||||||
|
assertTrue(customer.isPresent());
|
||||||
|
assertEquals(customerId, customer.get().getId());
|
||||||
|
assertEquals(customerName, customer.get().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenNotExistingId_whenFindById_thenReturnEmpty() {
|
||||||
|
FindIterable<Document> findIterable = mock(FindIterable.class);
|
||||||
|
ObjectId customerId = new ObjectId();
|
||||||
|
when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
|
||||||
|
when(findIterable.first()).thenReturn(null);
|
||||||
|
Optional<Customer<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
|
||||||
|
assertTrue(customer.isEmpty());
|
||||||
|
verify(customerCollection).find(Filters.eq("_id", customerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenDeleteSchema_thenDeleteCollection() {
|
||||||
|
mongoCustomerDAO.deleteSchema();
|
||||||
|
verify(customerCollection).drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.17.0</version>
|
<version>3.18.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
|||||||
@@ -46,12 +46,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
<version>2.19.0</version>
|
<version>2.21.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>2.18.3</version>
|
<version>2.22.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
|
|||||||
@@ -50,12 +50,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
<version>2.19.0</version>
|
<version>2.21.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>2.18.3</version>
|
<version>2.18.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -103,13 +103,12 @@ Use the Factory Method Pattern in Java when:
|
|||||||
|
|
||||||
## Real-World Applications of Factory Method Pattern in Java
|
## Real-World Applications of Factory Method Pattern in Java
|
||||||
|
|
||||||
* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
|
* [java.util.Calendar](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Calendar.html#getInstance()>)
|
||||||
* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
|
* [java.util.ResourceBundle](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ResourceBundle.html#getBundle(java.lang.String)>)
|
||||||
* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
|
* [java.text.NumberFormat](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/text/NumberFormat.html#getInstance()>)
|
||||||
* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
|
* [java.nio.charset.Charset](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/charset/Charset.html#forName(java.lang.String)>)
|
||||||
* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-)
|
* [java.net.URLStreamHandlerFactory](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/URLStreamHandlerFactory.html#createURLStreamHandler(java.lang.String)>)
|
||||||
* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-)
|
* [java.util.EnumSet](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/EnumSet.html#of(E)>)
|
||||||
* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--)
|
|
||||||
* Frameworks that run application components, configured dynamically at runtime.
|
* Frameworks that run application components, configured dynamically at runtime.
|
||||||
|
|
||||||
## Benefits and Trade-offs of Factory Method Pattern
|
## Benefits and Trade-offs of Factory Method Pattern
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
title: "Fallback Pattern in Java: Graceful Degradation in Microservices"
|
||||||
|
shortTitle: Fallback
|
||||||
|
description: "Learn about the Fallback pattern in Java design, which ensures microservice resilience and graceful system degradation when primary dependencies fail."
|
||||||
|
category: Resilience
|
||||||
|
language: en
|
||||||
|
tag:
|
||||||
|
- Cloud distributed
|
||||||
|
- Fault tolerance
|
||||||
|
- Microservices
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent of Fallback Design Pattern
|
||||||
|
|
||||||
|
The Fallback design pattern is a resiliency pattern used in microservices architecture to handle failures gracefully. It ensures that when a service is unavailable, fails, or times out, the system can continue to operate by providing an alternative response or executing a predefined fallback mechanism. This pattern enhances robustness and reliability by preventing cascading failures and improving the overall user experience.
|
||||||
|
|
||||||
|
## Detailed Explanation of Fallback Pattern with Real-World Examples
|
||||||
|
|
||||||
|
Real-world example
|
||||||
|
|
||||||
|
> Consider a movie streaming application like Netflix. The home page loads personalized recommendations for the logged-in user. If the recommendation microservice goes offline or is too slow, the user shouldn't see a broken page. Instead, the system falls back to a cached list of globally popular movies. While the response is degraded (not personalized), the application remains functional, providing a seamless user experience.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Fallback ensures that if a primary service call fails, the application falls back to a backup strategy (e.g. cached response, default value, or simplified service) rather than raising an error and failing completely.
|
||||||
|
|
||||||
|
Wikipedia says
|
||||||
|
|
||||||
|
> A fallback is a contingency option to be taken if the preferred choice is unavailable. In software, fallback mechanisms are crucial for fault tolerance, allowing systems to degrade gracefully rather than crash.
|
||||||
|
|
||||||
|
## Programmatic Example of Fallback Pattern in Java
|
||||||
|
|
||||||
|
This Java example demonstrates how the Fallback pattern can manage service failures, integrate with a Circuit Breaker, and apply timeout limits.
|
||||||
|
|
||||||
|
1. **Defining the Remote Service Interface**
|
||||||
|
|
||||||
|
The `RemoteService` interface represents any external dependency call.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface RemoteService {
|
||||||
|
String execute() throws Exception;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Defining the Primary Service and Fallback Service**
|
||||||
|
|
||||||
|
The `PrimaryService` simulates our main external dependency which may suffer from errors or latency. The `FallbackService` returns a cached or degraded static response.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Primary Service simulating latency and errors
|
||||||
|
var healthyPrimary = new PrimaryService("Healthy data from primary service", 10, false);
|
||||||
|
var failingPrimary = new PrimaryService("Failing service", 0, true);
|
||||||
|
var slowPrimary = new PrimaryService("Slow response from primary service", 500, false);
|
||||||
|
|
||||||
|
// Fallback Service providing degraded response
|
||||||
|
var fallback = new FallbackService("Fallback degraded/cached response");
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Monitoring Health with a Circuit Breaker**
|
||||||
|
|
||||||
|
A `SimpleCircuitBreaker` tracks the number of failures to trip the circuit to `OPEN`, bypassing the primary service immediately to avoid waiting for timeouts.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Trip after 2 failures; retry after 1 second
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(2, 1000);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Executing Calls with the FallbackExecutor**
|
||||||
|
|
||||||
|
The `FallbackExecutor` uses virtual threads to execute the primary service call. It applies timeouts, handles exceptions, records failures to the circuit breaker, and falls back to the fallback service as needed.
|
||||||
|
|
||||||
|
```java
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
// Scenario 1: Healthy primary service call
|
||||||
|
String response1 = executor.execute(healthyPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response: {}", response1); // Healthy data from primary service
|
||||||
|
|
||||||
|
// Scenario 2: Failing service call triggers fallback
|
||||||
|
String response2 = executor.execute(failingPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response: {}", response2); // Fallback degraded/cached response
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use the Fallback Pattern in Java
|
||||||
|
|
||||||
|
The Fallback pattern is applicable:
|
||||||
|
|
||||||
|
* In microservices architectures where dependencies are called over the network and are prone to network partitions, timeouts, and outages.
|
||||||
|
* When returning a default, empty, or cached value is preferable to failing the entire request.
|
||||||
|
* In user-facing systems where maintaining a working UI (even with degraded features) is critical for user satisfaction.
|
||||||
|
|
||||||
|
## Real-World Applications of Fallback Pattern in Java
|
||||||
|
|
||||||
|
* [Resilience4j Fallback mechanism](https://resilience4j.readme.io/docs/fallback)
|
||||||
|
* [Netflix Hystrix Fallback](https://github.com/Netflix/Hystrix/wiki/How-To-Use#Fallback)
|
||||||
|
* Spring Cloud Circuit Breaker integrations
|
||||||
|
|
||||||
|
## Benefits and Trade-offs of Fallback Pattern
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* **Graceful Degradation**: Improves user experience by returning partial/cached data instead of errors.
|
||||||
|
* **Cascading Failure Prevention**: Avoids blocking threads waiting on hung services.
|
||||||
|
* **Fault Tolerance**: Improves system uptime and reliability.
|
||||||
|
|
||||||
|
Trade-Offs:
|
||||||
|
|
||||||
|
* **Stale Data**: Fallback cached responses may present out-of-date information to the user.
|
||||||
|
* **Increased Complexity**: Requires writing alternative execution flows and testing fallback scenarios.
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
|
||||||
|
- [Circuit Breaker](https://github.com/iluwatar/java-design-patterns/tree/master/circuit-breaker): Restricts calls to failing services. Often wraps the primary service before fallback is triggered.
|
||||||
|
- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry): Retries failed calls before triggering the fallback.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>fallback</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.fallback.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Fallback design pattern is a resiliency pattern used in microservices architecture to handle
|
||||||
|
* failures gracefully. When a service is unavailable, fails, or times out, the system responds with
|
||||||
|
* a pre-configured fallback mechanism (like cached data or a simplified response).
|
||||||
|
*
|
||||||
|
* <p>This App demonstrates: 1. Healthy calls returning standard responses. 2. Failing calls
|
||||||
|
* (throwing exception) falling back to a fallback handler. 3. Latent calls (timing out) falling
|
||||||
|
* back. 4. Circuit Breaker tripping and immediately fast-failing to the fallback handler. 5.
|
||||||
|
* Recovery after a retry duration, returning the system to a healthy CLOSED state.
|
||||||
|
*/
|
||||||
|
public class App {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for the application.
|
||||||
|
*
|
||||||
|
* @param args Command line arguments (not used)
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
var healthyPrimary = new PrimaryService("Healthy data from primary service", 10, false);
|
||||||
|
var failingPrimary = new PrimaryService("Failing service", 0, true);
|
||||||
|
var slowPrimary = new PrimaryService("Slow response from primary service", 500, false);
|
||||||
|
var fallback = new FallbackService("Fallback degraded/cached response");
|
||||||
|
|
||||||
|
// Failure threshold is 2 failures, retry time period is 1 second (1000ms)
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(2, 1000);
|
||||||
|
|
||||||
|
// Scenario 1: Healthy primary service call
|
||||||
|
LOGGER.info("Scenario 1: Executing request to healthy primary service...");
|
||||||
|
String response1 = executor.execute(healthyPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response received: {}", response1);
|
||||||
|
LOGGER.info("Circuit Breaker State: {}\n", circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Scenario 2: Failing primary service call (fails and increments failure count to 1)
|
||||||
|
LOGGER.info("Scenario 2: Executing request to failing primary service (throws exception)...");
|
||||||
|
String response2 = executor.execute(failingPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response received: {}", response2);
|
||||||
|
LOGGER.info("Circuit Breaker State: {}\n", circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Scenario 3: Slow primary service call (times out and increments failure count to 2,
|
||||||
|
// tripping breaker)
|
||||||
|
LOGGER.info("Scenario 3: Executing request to slow primary service (triggers timeout)...");
|
||||||
|
String response3 = executor.execute(slowPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response received: {}", response3);
|
||||||
|
LOGGER.info("Circuit Breaker State: {}\n", circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Scenario 4: Fast failing when circuit is OPEN
|
||||||
|
LOGGER.info("Scenario 4: Executing request while Circuit Breaker is OPEN...");
|
||||||
|
String response4 = executor.execute(healthyPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response received: {}", response4);
|
||||||
|
LOGGER.info("Circuit Breaker State: {}\n", circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Scenario 5: Recovery from OPEN state
|
||||||
|
LOGGER.info("Scenario 5: Waiting for retry period to elapse...");
|
||||||
|
try {
|
||||||
|
Thread.sleep(1100); // Wait longer than retryTimePeriodMs (1000ms)
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
LOGGER.info(
|
||||||
|
"Circuit Breaker State (should be HALF_OPEN on next check): {}",
|
||||||
|
circuitBreaker.getState());
|
||||||
|
LOGGER.info("Executing request to healthy primary service to reset breaker...");
|
||||||
|
String response5 = executor.execute(healthyPrimary, fallback, circuitBreaker, 100);
|
||||||
|
LOGGER.info("Response received: {}", response5);
|
||||||
|
LOGGER.info("Circuit Breaker State (should be CLOSED): {}\n", circuitBreaker.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orchestrates primary service execution with timeouts, circuit breaker health checks, and fallback
|
||||||
|
* execution logic. Implements AutoCloseable to ensure thread pools are closed cleanly.
|
||||||
|
*/
|
||||||
|
public class FallbackExecutor implements AutoCloseable {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FallbackExecutor.class);
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
|
||||||
|
/** Constructor for FallbackExecutor. Initializes a virtual thread per task executor. */
|
||||||
|
public FallbackExecutor() {
|
||||||
|
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the primary service call. If it fails, times out, or the circuit breaker is open, it
|
||||||
|
* falls back to the fallback service.
|
||||||
|
*
|
||||||
|
* @param primary the primary service call to execute
|
||||||
|
* @param fallback the fallback service to call when primary fails or is bypassed
|
||||||
|
* @param circuitBreaker the circuit breaker monitoring service health
|
||||||
|
* @param timeoutMs timeout limit for the primary service in milliseconds
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String execute(
|
||||||
|
RemoteService primary,
|
||||||
|
RemoteService fallback,
|
||||||
|
SimpleCircuitBreaker circuitBreaker,
|
||||||
|
long timeoutMs) {
|
||||||
|
|
||||||
|
// 1. Check Circuit Breaker
|
||||||
|
if (circuitBreaker.getState() == SimpleCircuitBreaker.State.OPEN) {
|
||||||
|
LOGGER.warn("Circuit is OPEN. Fast-failing and calling fallback service.");
|
||||||
|
try {
|
||||||
|
return fallback.execute();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("Fallback service execution failed: {}", ex.getMessage());
|
||||||
|
return "Fallback Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Attempt service call with timeout
|
||||||
|
Callable<String> task = primary::execute;
|
||||||
|
Future<String> future = executorService.submit(task);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String result = future.get(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
circuitBreaker.recordSuccess();
|
||||||
|
return result;
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
LOGGER.error("Service call timed out. Triggering fallback.");
|
||||||
|
future.cancel(true); // Interrupt / cancel the task
|
||||||
|
circuitBreaker.recordFailure();
|
||||||
|
try {
|
||||||
|
return fallback.execute();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("Fallback service execution failed: {}", ex.getMessage());
|
||||||
|
return "Fallback Error";
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Service call failed with exception: {}. Triggering fallback.", e.getMessage());
|
||||||
|
circuitBreaker.recordFailure();
|
||||||
|
try {
|
||||||
|
return fallback.execute();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("Fallback service execution failed: {}", ex.getMessage());
|
||||||
|
return "Fallback Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A concrete implementation of the remote service representing the fallback handler. It is invoked
|
||||||
|
* when the primary service fails, returns a cached response or degrades gracefully.
|
||||||
|
*/
|
||||||
|
public class FallbackService implements RemoteService {
|
||||||
|
private final String fallbackResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for FallbackService.
|
||||||
|
*
|
||||||
|
* @param fallbackResponse the fallback response to return
|
||||||
|
*/
|
||||||
|
public FallbackService(String fallbackResponse) {
|
||||||
|
this.fallbackResponse = fallbackResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute() {
|
||||||
|
return fallbackResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A concrete implementation of the remote service representing the primary service. It can be
|
||||||
|
* configured to simulate latency and errors to test resilience features.
|
||||||
|
*/
|
||||||
|
public class PrimaryService implements RemoteService {
|
||||||
|
private final long latencyMs;
|
||||||
|
private final String response;
|
||||||
|
private final boolean shouldThrowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for PrimaryService.
|
||||||
|
*
|
||||||
|
* @param response the successful response to return
|
||||||
|
* @param latencyMs simulated latency in milliseconds
|
||||||
|
* @param shouldThrowException if true, the service will throw an exception
|
||||||
|
*/
|
||||||
|
public PrimaryService(String response, long latencyMs, boolean shouldThrowException) {
|
||||||
|
this.latencyMs = latencyMs;
|
||||||
|
this.response = response;
|
||||||
|
this.shouldThrowException = shouldThrowException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute() throws Exception {
|
||||||
|
if (shouldThrowException) {
|
||||||
|
throw new RuntimeException("Primary service failed!");
|
||||||
|
}
|
||||||
|
if (latencyMs > 0) {
|
||||||
|
Thread.sleep(latencyMs);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
/** Representation of a service (e.g. a microservice client) that might fail. */
|
||||||
|
public interface RemoteService {
|
||||||
|
/**
|
||||||
|
* Executes the service logic.
|
||||||
|
*
|
||||||
|
* @return the service response
|
||||||
|
* @throws Exception if service call fails or is interrupted
|
||||||
|
*/
|
||||||
|
String execute() throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/** A simplified circuit breaker implementation for tracking remote service health. */
|
||||||
|
public class SimpleCircuitBreaker {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleCircuitBreaker.class);
|
||||||
|
|
||||||
|
private final int failureThreshold;
|
||||||
|
private final long retryTimePeriodMs;
|
||||||
|
private int failureCount = 0;
|
||||||
|
private long lastFailureTime = 0;
|
||||||
|
private State state = State.CLOSED;
|
||||||
|
|
||||||
|
/** The state of the circuit breaker. */
|
||||||
|
public enum State {
|
||||||
|
CLOSED,
|
||||||
|
OPEN,
|
||||||
|
HALF_OPEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for SimpleCircuitBreaker.
|
||||||
|
*
|
||||||
|
* @param failureThreshold consecutive failure count threshold to trip the breaker
|
||||||
|
* @param retryTimePeriodMs time duration to wait in OPEN state before trying again (HALF_OPEN)
|
||||||
|
*/
|
||||||
|
public SimpleCircuitBreaker(int failureThreshold, long retryTimePeriodMs) {
|
||||||
|
this.failureThreshold = failureThreshold;
|
||||||
|
this.retryTimePeriodMs = retryTimePeriodMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current state of the circuit breaker after evaluating transitions.
|
||||||
|
*
|
||||||
|
* @return current state
|
||||||
|
*/
|
||||||
|
public synchronized State getState() {
|
||||||
|
evaluateState();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void evaluateState() {
|
||||||
|
if (state == State.OPEN) {
|
||||||
|
if (System.currentTimeMillis() - lastFailureTime > retryTimePeriodMs) {
|
||||||
|
state = State.HALF_OPEN;
|
||||||
|
LOGGER.info("Circuit Breaker transitioned to HALF_OPEN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Records a successful operation, resetting the failure counter and closing the circuit. */
|
||||||
|
public synchronized void recordSuccess() {
|
||||||
|
failureCount = 0;
|
||||||
|
state = State.CLOSED;
|
||||||
|
LOGGER.info("Circuit Breaker transitioned to CLOSED (success recorded)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Records a failure, potentially tripping the circuit to OPEN if threshold is met. */
|
||||||
|
public synchronized void recordFailure() {
|
||||||
|
failureCount++;
|
||||||
|
lastFailureTime = System.currentTimeMillis();
|
||||||
|
if (state == State.CLOSED && failureCount >= failureThreshold) {
|
||||||
|
state = State.OPEN;
|
||||||
|
LOGGER.warn("Circuit Breaker transitioned to OPEN (failure threshold reached)");
|
||||||
|
} else if (state == State.HALF_OPEN) {
|
||||||
|
state = State.OPEN;
|
||||||
|
LOGGER.warn("Circuit Breaker transitioned to OPEN (failed during HALF_OPEN)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Test verifying that App main method runs without throwing exceptions. */
|
||||||
|
class AppTest {
|
||||||
|
@Test
|
||||||
|
void testMain() {
|
||||||
|
assertDoesNotThrow(() -> App.main(new String[] {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 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.fallback;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Unit and integration tests for the Fallback design pattern. */
|
||||||
|
class FallbackPatternTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHealthyServiceCall() {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
var primary = new PrimaryService("Primary Response", 0, false);
|
||||||
|
var fallback = new FallbackService("Fallback Response");
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(2, 1000);
|
||||||
|
|
||||||
|
String response = executor.execute(primary, fallback, circuitBreaker, 100);
|
||||||
|
assertEquals("Primary Response", response);
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.CLOSED, circuitBreaker.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailingServiceCall() {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
var primary = new PrimaryService("Primary Response", 0, true);
|
||||||
|
var fallback = new FallbackService("Fallback Response");
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(2, 1000);
|
||||||
|
|
||||||
|
// First failure: should call fallback, state remains CLOSED since threshold is 2
|
||||||
|
String response1 = executor.execute(primary, fallback, circuitBreaker, 100);
|
||||||
|
assertEquals("Fallback Response", response1);
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.CLOSED, circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Second failure: should call fallback, state becomes OPEN
|
||||||
|
String response2 = executor.execute(primary, fallback, circuitBreaker, 100);
|
||||||
|
assertEquals("Fallback Response", response2);
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.OPEN, circuitBreaker.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTimeoutServiceCall() {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
// Primary takes 300ms, timeout limit is 50ms
|
||||||
|
var primary = new PrimaryService("Primary Response", 300, false);
|
||||||
|
var fallback = new FallbackService("Fallback Response");
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(1, 1000);
|
||||||
|
|
||||||
|
String response = executor.execute(primary, fallback, circuitBreaker, 50);
|
||||||
|
assertEquals("Fallback Response", response);
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.OPEN, circuitBreaker.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCircuitBreakerOpenFastFail() {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
// Configure primary service to throw exception if called
|
||||||
|
var primary = new PrimaryService("Primary Response", 0, true);
|
||||||
|
var fallback = new FallbackService("Fallback Response");
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(1, 1000);
|
||||||
|
|
||||||
|
// Force open by registering a failure
|
||||||
|
circuitBreaker.recordFailure();
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.OPEN, circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Now call execute. It should short-circuit and not call the failing primary (fast-fail).
|
||||||
|
String response = executor.execute(primary, fallback, circuitBreaker, 100);
|
||||||
|
assertEquals("Fallback Response", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCircuitBreakerRecovery() throws InterruptedException {
|
||||||
|
try (var executor = new FallbackExecutor()) {
|
||||||
|
var primary = new PrimaryService("Primary Response", 0, false);
|
||||||
|
var fallback = new FallbackService("Fallback Response");
|
||||||
|
// Failure threshold = 1, retry period = 100ms
|
||||||
|
var circuitBreaker = new SimpleCircuitBreaker(1, 100);
|
||||||
|
|
||||||
|
// Trip the breaker
|
||||||
|
circuitBreaker.recordFailure();
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.OPEN, circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Wait 150ms to exceed retry period
|
||||||
|
Thread.sleep(150);
|
||||||
|
|
||||||
|
// State should evaluate to HALF_OPEN
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.HALF_OPEN, circuitBreaker.getState());
|
||||||
|
|
||||||
|
// Execute. Healthy primary service succeeds, state transitions to CLOSED
|
||||||
|
String response = executor.execute(primary, fallback, circuitBreaker, 100);
|
||||||
|
assertEquals("Primary Response", response);
|
||||||
|
assertEquals(SimpleCircuitBreaker.State.CLOSED, circuitBreaker.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
title: "Fork/Join Pattern in Java: Parallel Divide-and-Conquer Processing"
|
||||||
|
shortTitle: Fork/Join
|
||||||
|
description: "Learn the Fork/Join design pattern in Java with real-world examples, class diagrams, and code samples. Understand how to split large tasks into parallel subtasks for improved performance."
|
||||||
|
category: Concurrency
|
||||||
|
language: en
|
||||||
|
tag:
|
||||||
|
- Performance
|
||||||
|
- Scalability
|
||||||
|
- Concurrency
|
||||||
|
---
|
||||||
|
|
||||||
|
## Also known as
|
||||||
|
|
||||||
|
* Divide and Conquer Parallelism
|
||||||
|
* Work-Stealing Parallelism
|
||||||
|
|
||||||
|
## Intent of Fork/Join Design Pattern
|
||||||
|
|
||||||
|
The Fork/Join pattern recursively splits a large task into independent subtasks (fork),
|
||||||
|
processes them in parallel across multiple threads, and combines their results (join) to
|
||||||
|
produce a final outcome. It maximizes CPU utilization for computationally intensive problems.
|
||||||
|
|
||||||
|
## Detailed Explanation of Fork/Join Pattern with Real-World Examples
|
||||||
|
|
||||||
|
Real-world example
|
||||||
|
|
||||||
|
> Imagine a large warehouse that needs to count all its inventory items across 100 aisles.
|
||||||
|
> Instead of one person counting every aisle sequentially, the manager divides the warehouse
|
||||||
|
> into sections and assigns a team of workers to count each section simultaneously. Once
|
||||||
|
> every section is counted, the manager collects all partial counts and sums them into the
|
||||||
|
> total inventory. This is the Fork/Join pattern: split the work, do it in parallel, merge
|
||||||
|
> the results.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Fork/Join splits a big problem into smaller pieces, solves each piece in parallel on
|
||||||
|
> separate threads, then combines all results back together.
|
||||||
|
|
||||||
|
## Programmatic Example of Fork/Join Pattern in Java
|
||||||
|
|
||||||
|
We demonstrate the pattern by computing the sum of a large array in parallel using Java's
|
||||||
|
built-in `ForkJoinPool` and `RecursiveTask`.
|
||||||
|
|
||||||
|
The `SumTask` is a recursive task that splits the array when it's too large:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class SumTask extends RecursiveTask<Long> {
|
||||||
|
|
||||||
|
private static final int THRESHOLD = 1000;
|
||||||
|
private final long[] numbers;
|
||||||
|
private final int start;
|
||||||
|
private final int end;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Long compute() {
|
||||||
|
int length = end - start;
|
||||||
|
|
||||||
|
if (length <= THRESHOLD) {
|
||||||
|
// Base case: sum directly
|
||||||
|
long sum = 0;
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
sum += numbers[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fork: split into two halves
|
||||||
|
int mid = start + length / 2;
|
||||||
|
SumTask leftTask = new SumTask(numbers, start, mid);
|
||||||
|
SumTask rightTask = new SumTask(numbers, mid, end);
|
||||||
|
|
||||||
|
leftTask.fork(); // run left half asynchronously
|
||||||
|
long rightResult = rightTask.compute(); // compute right half here
|
||||||
|
long leftResult = leftTask.join(); // wait for left half
|
||||||
|
|
||||||
|
// Join: combine results
|
||||||
|
return leftResult + rightResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ForkJoinSumCalculator` provides a clean API:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class ForkJoinSumCalculator {
|
||||||
|
|
||||||
|
private final ForkJoinPool pool;
|
||||||
|
|
||||||
|
public ForkJoinSumCalculator() {
|
||||||
|
this.pool = ForkJoinPool.commonPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long calculateSum(long[] numbers) {
|
||||||
|
SumTask task = new SumTask(numbers, 0, numbers.length);
|
||||||
|
return pool.invoke(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the example in `App`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
long result = calculator.calculateSum(numbers);
|
||||||
|
System.out.println("Fork/Join sum: " + result);
|
||||||
|
```
|
||||||
|
|
||||||
|
Program output:
|
||||||
|
|
||||||
|
```
|
||||||
|
Fork/Join sum: 50000005000000
|
||||||
|
Expected sum: 50000005000000
|
||||||
|
Correct: true
|
||||||
|
Time taken: 45 ms
|
||||||
|
Available processors: 8
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use the Fork/Join Pattern in Java
|
||||||
|
|
||||||
|
* When you have a large, CPU-intensive task that can be divided into independent subtasks.
|
||||||
|
* When the subtasks are roughly the same size and don't depend on each other.
|
||||||
|
* When you want to utilize multiple CPU cores without manually managing threads.
|
||||||
|
* When the problem naturally fits a divide-and-conquer strategy (e.g., sorting, searching,
|
||||||
|
numerical computation).
|
||||||
|
|
||||||
|
## When NOT to Use Fork/Join
|
||||||
|
|
||||||
|
* For I/O-bound tasks (network calls, file reads) — use virtual threads or async I/O instead.
|
||||||
|
* When subtasks are too small — the overhead of forking exceeds the benefit.
|
||||||
|
* When tasks have dependencies on each other and cannot run independently.
|
||||||
|
|
||||||
|
## Benefits and Trade-offs of Fork/Join Pattern
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Maximizes CPU utilization through work-stealing algorithm.
|
||||||
|
* Scales automatically with the number of available processors.
|
||||||
|
* Built into Java's standard library (`java.util.concurrent`) — no external dependencies.
|
||||||
|
* Clean recursive decomposition makes the code readable and maintainable.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* Overhead from task creation and thread management for very small problems.
|
||||||
|
* Requires tasks to be independent — shared mutable state introduces bugs.
|
||||||
|
* Choosing an appropriate threshold requires tuning for optimal performance.
|
||||||
|
* Debugging parallel code is inherently harder than sequential code.
|
||||||
|
|
||||||
|
## Related Java Design Patterns
|
||||||
|
|
||||||
|
* [Divide and Conquer](https://java-design-patterns.com/patterns/divide-and-conquer/):
|
||||||
|
Fork/Join is the parallel execution variant of the classic divide-and-conquer strategy.
|
||||||
|
* [Thread Pool](https://java-design-patterns.com/patterns/thread-pool/): Fork/Join uses a
|
||||||
|
specialized pool with work-stealing semantics.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
* [Java Documentation for ForkJoinPool](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)
|
||||||
|
* [Java Concurrency in Practice — Brian Goetz](https://amzn.to/4aRMruW)
|
||||||
|
* [Java Documentation for RecursiveTask](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveTask.html)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the terms of the MIT license, see LICENSE.md in the root
|
||||||
|
of this project's GitHub repository for the full license text.
|
||||||
|
|
||||||
|
Copyright © 2014-2024 Ilkka Seppälä
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>fork-join</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.forkjoin.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Fork/Join pattern is a concurrency design pattern that splits a large task into smaller
|
||||||
|
* subtasks (fork), processes them in parallel, and then combines the results (join).
|
||||||
|
*
|
||||||
|
* <p>In Java, this pattern is implemented using {@link java.util.concurrent.ForkJoinPool} and
|
||||||
|
* {@link java.util.concurrent.RecursiveTask}. Worker threads in the pool use a work-stealing
|
||||||
|
* algorithm — idle threads take tasks from busy threads — maximizing CPU utilization.
|
||||||
|
*
|
||||||
|
* <p>In this example, we demonstrate the pattern by computing the sum of a large array in parallel.
|
||||||
|
* The array is recursively split in half until each piece is small enough to sum directly, then
|
||||||
|
* results are combined back up.
|
||||||
|
*/
|
||||||
|
public final class App {
|
||||||
|
|
||||||
|
private App() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param args command line arguments, not used
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Create an array of 10 million numbers: [1, 2, 3, ..., 10_000_000]
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
|
||||||
|
|
||||||
|
// Calculate sum using Fork/Join
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
long result = calculator.calculateSum(numbers);
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// The expected sum of 1 to N is N*(N+1)/2
|
||||||
|
long expected = 10_000_000L * 10_000_001L / 2;
|
||||||
|
|
||||||
|
System.out.println("Fork/Join sum: " + result);
|
||||||
|
System.out.println("Expected sum: " + expected);
|
||||||
|
System.out.println("Correct: " + (result == expected));
|
||||||
|
System.out.println("Time taken: " + (endTime - startTime) + " ms");
|
||||||
|
System.out.println("Available processors: " + Runtime.getRuntime().availableProcessors());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ForkJoinSumCalculator provides a convenient API to sum an array of numbers using the Fork/Join
|
||||||
|
* framework. It creates a {@link ForkJoinPool}, submits a {@link SumTask}, and returns the computed
|
||||||
|
* sum.
|
||||||
|
*
|
||||||
|
* <p>The pool manages a set of worker threads that process subtasks in parallel. Idle threads can
|
||||||
|
* "steal" work from busy threads, maximizing CPU utilization.
|
||||||
|
*/
|
||||||
|
public class ForkJoinSumCalculator {
|
||||||
|
|
||||||
|
private final ForkJoinPool pool;
|
||||||
|
|
||||||
|
/** Creates a calculator using the common ForkJoinPool (uses all available CPU cores). */
|
||||||
|
public ForkJoinSumCalculator() {
|
||||||
|
this.pool = ForkJoinPool.commonPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a calculator with a specific number of threads.
|
||||||
|
*
|
||||||
|
* @param parallelism the number of worker threads to use
|
||||||
|
*/
|
||||||
|
public ForkJoinSumCalculator(int parallelism) {
|
||||||
|
this.pool = new ForkJoinPool(parallelism);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the sum of all elements in the array using Fork/Join parallelism.
|
||||||
|
*
|
||||||
|
* @param numbers the array of numbers to sum
|
||||||
|
* @return the total sum of all elements
|
||||||
|
*/
|
||||||
|
public long calculateSum(long[] numbers) {
|
||||||
|
if (numbers == null || numbers.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
SumTask task = new SumTask(numbers, 0, numbers.length);
|
||||||
|
return pool.invoke(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import java.util.concurrent.RecursiveTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SumTask demonstrates the Fork/Join pattern by recursively splitting an array summation problem
|
||||||
|
* into smaller subtasks until each subtask is small enough to compute directly.
|
||||||
|
*
|
||||||
|
* <p>How it works: If the portion of the array is smaller than THRESHOLD, sum it in a simple loop.
|
||||||
|
* Otherwise, split the array in half, fork one half to run in parallel, compute the other half in
|
||||||
|
* the current thread, and then join the results. This approach utilizes multiple CPU cores to
|
||||||
|
* perform the summation significantly faster than a single-threaded loop for large arrays.
|
||||||
|
*/
|
||||||
|
public class SumTask extends RecursiveTask<Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the number of elements to process is at or below this threshold, the task computes the sum
|
||||||
|
* directly instead of splitting further.
|
||||||
|
*/
|
||||||
|
private static final int THRESHOLD = 1000;
|
||||||
|
|
||||||
|
private final long[] numbers;
|
||||||
|
private final int start;
|
||||||
|
private final int end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a task to sum elements of the given array from index {@code start} (inclusive) to index
|
||||||
|
* {@code end} (exclusive).
|
||||||
|
*
|
||||||
|
* @param numbers the array of numbers to sum
|
||||||
|
* @param start the starting index (inclusive)
|
||||||
|
* @param end the ending index (exclusive)
|
||||||
|
*/
|
||||||
|
public SumTask(long[] numbers, int start, int end) {
|
||||||
|
if (start > end) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"start (" + start + ") must not be greater than end (" + end + ")");
|
||||||
|
}
|
||||||
|
this.numbers = numbers;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main computation method. This is where the fork/join magic happens.
|
||||||
|
*
|
||||||
|
* @return the sum of elements from start to end
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Long compute() {
|
||||||
|
int length = end - start;
|
||||||
|
|
||||||
|
// BASE CASE: if the chunk is small enough, just sum directly
|
||||||
|
if (length <= THRESHOLD) {
|
||||||
|
return computeDirectly();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FORK: split the task into two halves
|
||||||
|
int mid = start + length / 2;
|
||||||
|
|
||||||
|
// Create subtask for the left half
|
||||||
|
SumTask leftTask = new SumTask(numbers, start, mid);
|
||||||
|
|
||||||
|
// Create subtask for the right half
|
||||||
|
SumTask rightTask = new SumTask(numbers, mid, end);
|
||||||
|
|
||||||
|
// Fork the left task — it will run in a separate thread
|
||||||
|
leftTask.fork();
|
||||||
|
|
||||||
|
// Compute the right task in the current thread (no need to fork both)
|
||||||
|
long rightResult = rightTask.compute();
|
||||||
|
|
||||||
|
// JOIN: wait for the left task to finish and get its result
|
||||||
|
long leftResult = leftTask.join();
|
||||||
|
|
||||||
|
// Combine the results from both halves
|
||||||
|
return leftResult + rightResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the sum directly using a simple loop. This is used when the chunk size is at or below
|
||||||
|
* the threshold — no further splitting needed.
|
||||||
|
*
|
||||||
|
* @return the sum of elements in the range [start, end)
|
||||||
|
*/
|
||||||
|
private long computeDirectly() {
|
||||||
|
long sum = 0;
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
sum += numbers[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AppTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExecuteWithoutException() {
|
||||||
|
App.main(new String[] {});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class ForkJoinSumCalculatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnZeroForNullArray() {
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
|
||||||
|
assertEquals(0L, calculator.calculateSum(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnZeroForEmptyArray() {
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
|
||||||
|
assertEquals(0L, calculator.calculateSum(new long[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCalculateSumOfSmallArray() {
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
long[] numbers = {10, 20, 30, 40, 50};
|
||||||
|
|
||||||
|
long result = calculator.calculateSum(numbers);
|
||||||
|
|
||||||
|
assertEquals(150L, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCalculateSumOfLargeArray() {
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 100_000).toArray();
|
||||||
|
|
||||||
|
long result = calculator.calculateSum(numbers);
|
||||||
|
|
||||||
|
long expected = 100_000L * 100_001L / 2;
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithCustomParallelism() {
|
||||||
|
// Use only 2 threads
|
||||||
|
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator(2);
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 50_000).toArray();
|
||||||
|
|
||||||
|
long result = calculator.calculateSum(numbers);
|
||||||
|
|
||||||
|
long expected = 50_000L * 50_001L / 2;
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.iluwatar.forkjoin;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class SumTaskTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSumSmallArrayDirectly() {
|
||||||
|
// Array smaller than threshold — should compute without forking
|
||||||
|
long[] numbers = {1, 2, 3, 4, 5};
|
||||||
|
SumTask task = new SumTask(numbers, 0, numbers.length);
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
assertEquals(15L, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSumLargeArrayUsingForkJoin() {
|
||||||
|
// Array larger than threshold — will fork into subtasks
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 10_000).toArray();
|
||||||
|
SumTask task = new SumTask(numbers, 0, numbers.length);
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
// Sum of 1 to N = N*(N+1)/2
|
||||||
|
long expected = 10_000L * 10_001L / 2;
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSumPartialRange() {
|
||||||
|
// Sum only a portion of the array (indices 2 to 5)
|
||||||
|
long[] numbers = {10, 20, 30, 40, 50, 60};
|
||||||
|
SumTask task = new SumTask(numbers, 2, 5);
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
// 30 + 40 + 50 = 120
|
||||||
|
assertEquals(120L, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnZeroForEmptyRange() {
|
||||||
|
long[] numbers = {1, 2, 3};
|
||||||
|
SumTask task = new SumTask(numbers, 1, 1); // start == end, empty range
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
assertEquals(0L, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleSingleElement() {
|
||||||
|
long[] numbers = {42};
|
||||||
|
SumTask task = new SumTask(numbers, 0, 1);
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
assertEquals(42L, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProduceCorrectResultForMillionElements() {
|
||||||
|
long[] numbers = LongStream.rangeClosed(1, 1_000_000).toArray();
|
||||||
|
SumTask task = new SumTask(numbers, 0, numbers.length);
|
||||||
|
|
||||||
|
long result = ForkJoinPool.commonPool().invoke(task);
|
||||||
|
|
||||||
|
long expected = 1_000_000L * 1_000_001L / 2;
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionWhenStartGreaterThanEnd() {
|
||||||
|
long[] numbers = {1, 2, 3, 4, 5};
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new SumTask(numbers, 4, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<version>3.27.3</version>
|
<version>3.27.7</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
public class App {
|
public class App {
|
||||||
/** Program entry point. */
|
/** Program entry point. */
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(App.class, args);
|
var context = SpringApplication.run(App.class, args);
|
||||||
|
if (args.length > 0 && "test".equals(args[0])) {
|
||||||
|
// Close the context immediately during tests to prevent Tomcat/background threads from
|
||||||
|
// hanging the JVM
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded 100 of 425 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user