Compare commits

...
97 Commits
Author SHA1 Message Date
DoksanbirandIlkka Seppälä 4cabb204f2 feat: add Microservices Bulkhead pattern (#3228) (#3597)
* feat: add Microservices Bulkhead pattern (#3228)

* fix: cancel queued calls on bulkhead shutdown

Shutdown now cancels the queued futures so callers do not hang, a shutdown racing a submit is no longer counted as a rejection, and interrupted inventory checks are logged. The class diagram is a rendered PNG.

---------

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2026-09-13 09:53:54 +03:00
Doksanbir dd24a29f80 feat: add Microservices Load Shedding pattern (#3229) (#3599)
* feat: add Microservices Load Shedding pattern (#3229)

* fix: harden load shedder release and report the admitted count

release() clamps the in-flight count at zero, acquire() returns the admitted count that the service logs, and the demo summary reports the total shed. The class diagram is a rendered PNG.
2026-09-13 09:29:24 +03:00
allcontributors[bot] d8e3ad7898 docs: add kaizencode-art as a contributor for translation (#3609)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-09-13 09:04:55 +03:00
KaizenCode 8807d0f537 Fix/fr localization assets (#3603)
* docs: fr: add missing pattern images and fix acyclic-visitor folder name

* docs: fr: rename localization folders to match upstream pattern directories
2026-09-13 09:03:58 +03:00
dependabot[bot] 41625d8d35 build(deps): bump org.apache.kafka:kafka-clients (#3574)
Bumps org.apache.kafka:kafka-clients from 3.6.1 to 3.9.2.

---
updated-dependencies:
- dependency-name: org.apache.kafka:kafka-clients
  dependency-version: 3.9.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 14:52:26 +03:00
dependabot[bot] 6055caf34a build(deps): bump com.fasterxml.jackson.core:jackson-databind (#3573)
Bumps [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) from 2.16.1 to 2.18.9.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.18.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 14:47:21 +03:00
Mukul Howale 3dad8220bb feat: transactional outbox pattern (#3589)
* Add transactional-outbox module

Introduce a new transactional-outbox Maven module demonstrating the Transactional Outbox pattern. Adds a Spring Boot sample app and README explaining the pattern. New code includes Order and OutboxEvent entities, OrderStatus/EventStatus enums, Spring Data repositories (OrderRepository, OutboxRepository), OrderService (atomic write of order + outbox), OutboxPublisher (scheduled poll & publish), MessageBroker interface and MessageConsumer implementation, and App entrypoint. Includes unit tests (AppTest, OrderServiceTest, OutboxPublisherTest). Root pom.xml updated to register the new module. Module uses Spring Data JPA, Spring Web, Lombok, H2 and standard test dependencies.

* Add OutboxPublisher edge-case tests

Add two unit tests for OutboxPublisher: one verifies processOutboxEvents returns an empty list when there are no pending events; the other simulates a MessageBroker exception to ensure the event status is set to FAILED and the repository.save is called. Added necessary Mockito and assertion imports to support the tests.

* Use MockitoExtension for OutboxPublisher test

Replace the legacy inline/mock-based test with a JUnit5 + MockitoExtension unit test. Adds OutboxPublisherUnitTest (uses @ExtendWith(MockitoExtension.class), @Mock and @InjectMocks) that verifies broker exceptions mark events as FAILED and that the repository.save(...) is called. Removes the duplicate inline-mocking test and unused Mockito imports from OutboxPublisherTest.

* Replace Lombok @Slf4j with explicit Logger

Remove Lombok's @Slf4j and add explicit org.slf4j.Logger/LoggerFactory fields in transactional-outbox classes.
This makes logging explicit and removes reliance on Lombok's @Slf4j annotation.

* Add scheduled wrapper for outbox publishing

Introduce publishOutboxEvents() annotated with @Scheduled(fixedDelay = 5000) that delegates to the existing processOutboxEvents(). This separates the scheduling concern from the transactional processing method; processOutboxEvents() remains @Transactional and now only handles fetching and dispatching pending OutboxEvent items.

* Use UTC timestamps and add OutboxPublisher ctor

Use UTC for timestamps in OrderService and OutboxPublisher by switching LocalDateTime.now() to LocalDateTime.now(ZoneOffset.UTC). Replace Lombok @RequiredArgsConstructor on OutboxPublisher with an explicit constructor for dependency injection and remove the @Transactional annotation from processOutboxEvents. Update processedAt assignment to use UTC as well.
2026-08-30 14:02:32 +03:00
Ilkka Seppälä 7c0abffe05 docs: Add SerpApi to Supporters (#3591)
* Update README.md
* Add logos
2026-08-30 09:28:23 +03:00
allcontributors[bot] 126ad5fa1f docs: add zanarellidev as a contributor for translation (#3587)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-25 07:52:05 +03:00
zanarelli c8423198cd docs: pt: add localization for polling-publisher, money, delegation patterns (#3583)
Add Brazilian Portuguese (pt) translations for three previously
untranslated pattern READMEs, following the existing localization/pt/
convention documented in the wiki (15. Support for multiple languages).
Frontmatter and section headers follow the same structure used by the
most recently updated existing translation (singleton), keeping code
blocks, links, and image references unchanged from the English source.
2026-08-25 07:51:26 +03:00
allcontributors[bot] d7327917cf docs: add devikae as a contributor for code (#3586)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-25 07:48:14 +03:00
dev-ikae b0c34db084 feat: Implement Write-Ahead Log (WAL) pattern (#3576) (#3582)
* feat: Implement Write-Ahead Log (WAL) pattern (#3576)

* test: Add exception handling tests and verify coverage
2026-08-25 07:47:35 +03:00
allcontributors[bot] aa11f6b6be docs: add SandhyaDevadiga as a contributor for code (#3585)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-25 07:39:42 +03:00
Sandhya 5698dea49b feat: Add Fork/Join design pattern (#3227) (#3550)
* Add fork-join pattern implementation

* Add input validation for start > end in SumTask

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>

* Add fork-join module to parent pom.xml

* Add missing assertThrows import in SumTaskTest

* Format fork-join code with Spotless

* Add AppTest to satisfy coverage requirements

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>

---------

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>
2026-08-25 07:39:07 +03:00
dependabot[bot] 4f28d4eb5b build(deps): bump io.zipkin.reporter2:zipkin-reporter-brave (#3579)
Bumps [io.zipkin.reporter2:zipkin-reporter-brave](https://github.com/openzipkin/zipkin-reporter-java) from 3.5.0 to 3.5.3.
- [Release notes](https://github.com/openzipkin/zipkin-reporter-java/releases)
- [Changelog](https://github.com/openzipkin/zipkin-reporter-java/blob/master/RELEASE.md)
- [Commits](https://github.com/openzipkin/zipkin-reporter-java/compare/3.5.0...3.5.3)

---
updated-dependencies:
- dependency-name: io.zipkin.reporter2:zipkin-reporter-brave
  dependency-version: 3.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 07:30:43 +03:00
dependabot[bot] 22a34127d0 build(deps): bump com.fasterxml.jackson.core:jackson-databind (#3567)
Bumps [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) from 2.22.0 to 2.22.1.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.22.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 22:16:49 +03:00
allcontributors[bot] 8b079dba5c docs: add Mukul-Howale as a contributor for code (#3572)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-16 22:13:01 +03:00
Mukul Howale a563344a35 feat: microservice messaging pattern (#3564)
* Initialize microservices-messaging Spring Boot project

Add initial project structure for microservices-messaging using Spring Boot. Includes Maven configuration with dependencies for Kafka, Lombok, and testing, as well as main application, test class, and application properties.

* Initialize microservices messaging pattern

1. Added initial project structure for demonstrating the microservices messaging pattern.
2. Introduced service stubs (OrderService, InventoryService, PaymentService, NotificationService), a Message and MessageBroker class, and a main App entry point.
3. Added README and logging configuration.

* Implement Message and MessageBroker classes

1. Added the Message class with unique ID, content, and timestamp fields, and a toString method.
2. Implemented the MessageBroker class to support topic-based publish-subscribe messaging, including subscriber management, message publishing, and logging.

* Implement messaging pattern for microservices

1. Added message handling logic to InventoryService, PaymentService, and NotificationService.
2. OrderService now publishes order events to a MessageBroker, and App demonstrates the messaging workflow. 3. Each service processes relevant order events and logs actions for demonstration purposes.

* Expand microservices messaging docs and add diagrams

1. Enhanced the README with detailed explanations, real-world examples, Java code samples, and references for the Microservices Messaging pattern.
2. Added flowchart and sequence diagram images to illustrate the pattern.

* Refactor to use Kafka for microservices messaging

1. Added Apache Kafka for asynchronous communication between services.
2. Added KafkaMessageProducer and KafkaMessageConsumer classes, updated service implementations and main application logic to use Kafka, and adjusted the Maven configuration to include Kafka and Jackson dependencies.
3. Updated and moved all classes to the com.iluwatar.messaging package, improved documentation, and updated diagrams to reflect the new architecture.

* Add unit tests and license headers to messaging module

1. Added comprehensive unit tests for App, InventoryService, KafkaMessageConsumer, KafkaMessageProducer, Message, NotificationService, OrderService, and PaymentService.
2. Added MIT license headers to all main source files and logback.xml.
3. Updated pom.xml to include JUnit Jupiter as a test dependency.

* Refactor and simplify service and Kafka test classes

1. Simplified unit tests for InventoryService, NotificationService, and PaymentService by removing null content tests and adding instantiation checks.
2. Refactored KafkaMessageConsumerTest and KafkaMessageProducerTest to avoid requiring a real Kafka instance, focusing on class structure and method existence instead of integration behavior.

* Add microservices-messaging module and run scripts

Introduce the microservices-messaging module and register it in the root pom.xml. Add docker-compose.yml to run a local Kafka (confluentinc/cp-kafka) with a healthcheck, plus run-app.ps1 and run-app.sh helper scripts that start Kafka if needed and then launch the application. Update the module README with usage instructions. Also remove a duplicated junit-jupiter-api test dependency from the module pom to rely on project defaults.

* microservices-messaging: code style and Lombok

Add Lombok as a provided dependency and apply formatting/refactoring across the microservices-messaging module. Changes include import reordering, Javadoc and logging formatting, consistent lambda/try/catch indentation, small Kafka consumer/producer refinements (Duration/Properties usage and callback formatting), Message class tweaks (@Getter, JSON ctor and toString formatting) and EOF/newline fixes. Unit tests were also reformatted for consistency. These are non-functional style and readability improvements; no behavior changes intended.

* Make Kafka producer/consumer testable

Refactor KafkaMessageProducer and KafkaMessageConsumer to depend on the Producer/Consumer interfaces and add constructors that accept mockable instances. Extract default producer/consumer creation into factory methods so tests can inject MockProducer/MockConsumer. Update tests across the microservices-messaging module to use MockProducer/MockConsumer, add more meaningful assertions, error/interrupt handling tests, and simplify AppTest. These changes improve unit testability and remove the need for a running Kafka instance while preserving runtime behavior.

* Format test Javadoc and reorder imports

Normalize Javadoc formatting and reorder static JUnit imports for consistency in messaging tests. Converted multi-line test Javadocs to single-line comments and adjusted import ordering in the following files:

- microservices-messaging/src/test/java/com/iluwatar/messaging/KafkaMessageConsumerTest.java
- microservices-messaging/src/test/java/com/iluwatar/messaging/KafkaMessageProducerTest.java
- microservices-messaging/src/test/java/com/iluwatar/messaging/OrderServiceTest.java

No functional changes.

* Add MIT headers and exclude .ps1 from license checks

Add MIT license headers to microservices-messaging/docker-compose.yml, run-app.ps1 and run-app.sh to ensure license text is present in these scripts. Update root pom.xml to exclude PowerShell (*.ps1) files from the license plugin checks so those files are not processed by the license rule.

* Add Kafka docker service to CI workflows

Bring up Kafka for tests in CI and PR workflows. Adds steps to run docker compose for microservices-messaging, poll Kafka readiness (up to 20 retries), and always tear down with docker compose down. Enables Maven tests that depend on Kafka. Affects .github/workflows/maven-ci.yml and .github/workflows/maven-pr-builder.yml.

* Use kafka-topics instead of kafka-topics.sh

Replace calls to kafka-topics.sh with kafka-topics in CI workflows and the Docker Compose healthcheck. Updated .github/workflows/maven-ci.yml, .github/workflows/maven-pr-builder.yml, and microservices-messaging/docker-compose.yml to use the kafka-topics binary for readiness checks and healthchecks. This prevents failures on images that expose the kafka-topics command without the .sh wrapper.

* Use docker compose --wait for Kafka startup

Replace the custom bash readiness loop with `docker compose ... up -d --wait` in CI and PR workflows. This simplifies startup of the microservices-messaging Kafka service and removes the manual retry/polling logic. Files changed: .github/workflows/maven-ci.yml, .github/workflows/maven-pr-builder.yml. Note: requires a Docker Compose version that supports the `--wait` flag.

* Reformat tests for readability

Reformatted KafkaMessageConsumerTest and PaymentServiceTest for readability and consistent formatting: reflowed constructor invocation, expanded anonymous HashMap and schedulePollTask lambda blocks, and aligned assertDoesNotThrow parameters. These are pure style changes with no behavioral modifications.

* Remove Kafka Docker Compose steps from CI

Removed the Start/Stop Kafka Docker Service steps that ran docker compose for microservices-messaging from .github/workflows/maven-ci.yml and .github/workflows/maven-pr-builder.yml. Workflows no longer start or tear down the Kafka docker-compose service during CI/PR runs; other steps (xvfb install, Maven build, Codecov upload, Sonar cache) remain unchanged.

* Bump google-java-format to 1.27.0; tidy tests

Update pom.xml to use google-java-format 1.27.0 (was 1.17.0). Make minor formatting cleanups in KafkaMessageConsumerTest and PaymentServiceTest by collapsing multi-line calls into single lines. No functional changes.

* Downgrade Google Java Format to 1.17.0

Update Spotless googleJavaFormat version in root pom.xml from 1.27.0 to 1.17.0

* Improve Kafka producer and consumer tests

Enhance microservices-messaging tests: add consumer tests to verify run() exits immediately when stopped and gracefully handles a WakeupException (ensuring the MockConsumer is closed). Update producer error test to avoid try-with-resources, assert publish behavior and history, trigger failingProducer.errorNext(...) before closing to cover the error callback branch, then close and assert the mock producer is closed.

* Refactor App.run and add messaging tests

Extract App.run(...) and introduce a package-private sleepMs field to control sleep durations so the demo can be exercised programmatically and sped up for tests. Main now delegates to run; consumers are started there. Tests updated: AppTest sets sleepMs to 0 and adds testRunWithMockObjects using MockProducer/MockConsumer to exercise run without a Kafka broker; KafkaMessageProducerTest adds a null-message publish test; minor formatting fix in KafkaMessageConsumerTest. These changes improve testability and coverage for the microservices-messaging module.
2026-08-16 22:12:29 +03:00
dependabot[bot] 8c7a3105df build(deps): bump com.fasterxml.jackson.core:jackson-databind (#3549)
Bumps [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) from 2.18.3 to 2.18.9.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.18.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 21:59:12 +03:00
allcontributors[bot] 29442249f8 docs: add AnveshSrivastava as a contributor for code (#3571)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-16 21:57:31 +03:00
Anvesh Srivastava 1673930bc0 feat: Add Backends For Frontends (BFF) pattern (#300) (#3543)
* Add Backends For Frontends pattern (#300)

Implements the BFF pattern with two client-specific gateways
(MobileBff, DesktopBff) aggregating shared downstream services
(AuthService, CartService, OrderService, SupplierService) into
client-tailored response shapes.

Closes #300

* Rename SupplierService lookup key to productName for clarity

The service is looked up by product name throughout the codebase, not
an actual product ID as the previous naming implied.

* Address review feedback: remove unnecessary compact constructors, add InMemory* service tests
2026-08-16 21:56:51 +03:00
dependabot[bot] c298e94d96 build(deps): bump ch.qos.logback:logback-core in /polling-publisher (#3546)
Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.5.18 to 1.5.34.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.18...v_1.5.34)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-version: 1.5.34
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 21:50:34 +03:00
dependabot[bot] d498332345 build(deps): bump ch.qos.logback:logback-core in /subclass-sandbox (#3545)
Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.5.34 to 1.5.35.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.34...v_1.5.35)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-version: 1.5.35
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 21:49:02 +03:00
allcontributors[bot] 653bced988 docs: add MohamedMBG as a contributor for code (#3570)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-16 21:47:52 +03:00
BAGHDAD Mohamed 5d6ff2f0c4 feat: add rule engine design pattern (#3548)
Add an educational rule-engine module demonstrating the Rule Engine
pattern. Each business rule is an independent object exposing evaluate
(decision) and execute (action); a RuleEngine evaluates a collection of
rules against an immutable loan-application context in insertion order,
runs the passing rules, and reports every passed and failed rule via an
immutable RuleEngineResult. Includes three concrete rules, an App demo,
unit tests, README, and class diagram.
2026-08-16 21:47:18 +03:00
allcontributors[bot] a7000fe61b docs: add arnabnandy7 as a contributor for doc (#3569)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-16 21:43:53 +03:00
Arnab Nandy 47693507db docs: Update factory method Java API links (#3539)
* Update factory method Java API links

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>

* Trigger PR checks

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>

---------

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>
2026-08-16 21:43:10 +03:00
dependabot[bot] f5bdf688f1 build(deps): bump com.diffplug.spotless:spotless-maven-plugin (#3540)
Bumps [com.diffplug.spotless:spotless-maven-plugin](https://github.com/diffplug/spotless) from 2.44.2 to 3.8.0.
- [Release notes](https://github.com/diffplug/spotless/releases)
- [Changelog](https://github.com/diffplug/spotless/blob/main/CHANGES.md)
- [Commits](https://github.com/diffplug/spotless/compare/maven/2.44.2...maven/3.8.0)

---
updated-dependencies:
- dependency-name: com.diffplug.spotless:spotless-maven-plugin
  dependency-version: 3.8.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 21:37:59 +03:00
allcontributors[bot] 401c44b797 docs: add priyanshuvishwakarma273403 as a contributor for code (#3568)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-08-16 21:32:18 +03:00
PriyanshuandIlkka Seppälä 7e0ddb01cd feat: Implement Fallback design pattern #2846 (#3538)
* feat: implement Fallback design pattern with circuit breaker and service monitoring

* docs: add README for Fallback design pattern implementation

---------

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2026-08-16 21:31:20 +03:00
dependabot[bot] f1dcddfd3a build(deps): bump com.fasterxml.jackson.core:jackson-databind (#3533)
Bumps [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) from 2.18.3 to 2.22.0.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.22.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 20:33:44 +03:00
Hattori Henzo 688095a4c7 feat: Add the implementation of Onion-Architecture (#3532)
* Add the implementation of Onion-Architecture

* Execute maven command: mvn spotless:apply
2026-08-16 20:30:42 +03:00
allcontributors[bot] b55fc2bcb5 docs: add SrGiuliano77 as a contributor for translation (#3537)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-07-04 21:26:44 +03:00
Giuliano Oliveira 74d2dbe0be docs: Translation of the README.md file for active-object to pt (#2553) 2026-07-04 21:26:07 +03:00
dependabot[bot] f179974422 build(deps-dev): bump de.flapdoodle.embed:de.flapdoodle.embed.mongo (#3528)
Bumps [de.flapdoodle.embed:de.flapdoodle.embed.mongo](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo) from 4.20.0 to 4.33.0.
- [Commits](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/compare/de.flapdoodle.embed.mongo-4.20.0...de.flapdoodle.embed.mongo-4.33.0)

---
updated-dependencies:
- dependency-name: de.flapdoodle.embed:de.flapdoodle.embed.mongo
  dependency-version: 4.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 21:22:08 +03:00
dependabot[bot] 448c8b5d21 build(deps): bump org.slf4j:slf4j-api from 2.0.9 to 2.0.18 (#3525)
Bumps org.slf4j:slf4j-api from 2.0.9 to 2.0.18.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 21:21:10 +03:00
OrbisAI SecurityandClaude Sonnet 4.6 fcdf639b15 fix: remove unsafe exec() in page controller App.java (#3471)
* fix: V-002 security vulnerability

Automated security fix generated by Orbis Security AI

* adding cross-platform support

* fixing sonarqube hotspots

* fixing the formating for java windows

* fix: address PR review comments - Windows title bug, Unix paths, and consistency

- Add empty string title arg to cmd /c start to fix paths-with-spaces bug
- Revert Unix commands to bare names (open, xdg-open) with NOSONAR since
  xdg-open location varies by distro; absolute paths broke portability
- Apply same ProcessBuilder fix to page-object/src App.java for consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve Windows path via SystemRoot env var instead of hardcoding

C:\Windows\System32\cmd.exe fails when Windows is installed on a
non-C drive. Use System.getenv("SystemRoot") with a C:\Windows
fallback to locate cmd.exe portably in both App.java files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 19:09:13 +03:00
dependabot[bot] 39dce8eedf build(deps): bump org.springframework.boot:spring-boot-starter-thymeleaf (#3520)
Bumps [org.springframework.boot:spring-boot-starter-thymeleaf](https://github.com/spring-projects/spring-boot) from 3.4.4 to 4.0.6.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.4...v4.0.6)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-thymeleaf
  dependency-version: 4.0.6
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 19:02:44 +03:00
dependabot[bot] 9c5d703dda build(deps): bump ch.qos.logback:logback-classic from 1.5.18 to 1.5.34 (#3517)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.18 to 1.5.34.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.18...v_1.5.34)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 18:56:32 +03:00
Ilkka Seppälä 3b9a2df119 fix: website build failed 2026-06-07 19:32:28 +03:00
Ilkka Seppälä 7b4482f08c refactor: remove redundant compiler property declarations from module POM files 2026-06-07 14:00:45 +03:00
dependabot[bot] 3b758da41a build(deps-dev): bump org.assertj:assertj-core in /rate-limiting-pattern (#3505)
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.24.2 to 3.27.7.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.24.2...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-07 13:44:31 +03:00
allcontributors[bot] 0f67a61be1 docs: add AnakinYen0329 as a contributor for translation (#3513)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-07 13:17:28 +03:00
Anakin Yen 5a2beb8808 feat: translage to traditional Chinese (zh-tw) (#3324)
* 修改完成

* Add Singleton pattern example
2026-06-07 13:16:48 +03:00
Ved Asole 4cc30055fc docs: Update ved-asole link in .all-contributorsrc (#3294)
* Update ved-asole link in .all-contributorsrc

Updated my profile link in .all-contributorsrc

* Update README.md with url for ved-asole profile

Update README.md with url for ved-asole profile
2026-06-07 13:09:39 +03:00
Ilkka Seppälä 2dfb27c859 fix: spotless formatting fixes 2026-06-07 12:40:02 +03:00
Ilkka Seppälä c317ce012a feat: Enable codecov reports 2026-06-07 12:35:18 +03:00
Ilkka Seppälä 177dc52704 fix: Fix test stability issues 2026-06-07 12:10:39 +03:00
OrbisAI Security 13cde46f2e fix: CVE-2025-22235 security vulnerability (#3510)
Automated dependency upgrade by OrbisAI Security
2026-06-07 10:11:45 +03:00
allcontributors[bot] c70b63d069 docs: add alex052525 as a contributor for code (#3511)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-06 22:06:25 +03:00
김정모andClaude Sonnet 4.6 7473378ca2 feat: implement Immutable pattern (Fixes #3448) (#3509)
* feat: implement Immutable pattern #3448

Add immutable/ module with ImmutableUser (final class, defensive copy,
withAge wither method), App demo, JUnit 5 tests, PlantUML diagram, and
README following project conventions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add withName and withRoles wither methods to ImmutableUser #3448

Every field now has a corresponding wither for consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add junit-jupiter-api dependency to immutable module #3448

Required for @Test annotations and assert methods in test code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 22:05:42 +03:00
dependabot[bot] d4a0ece9e3 build(deps-dev): bump org.assertj:assertj-core in /health-check (#3438)
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.7.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 21:03:39 +03:00
allcontributors[bot] 1288720893 docs: add rranjangupta as a contributor for code (#3508)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-03 21:00:32 +03:00
Raju Gupta 8b0cf692cf feat: Add test coverage for single-table-inheritance pattern (#3468)
* Add STI tests

* test: add delta for floating-point comparison in VehicleServiceTest

* fix: guard teardown against nulls and use assertNotNull in VehicleServiceTest

Add null checks in @AfterEach tearDown() to prevent NullPointerException
if setUp() partially fails. Replace assertTrue(x != null) with assertNotNull(x).

* style: format code in SingleTableInheritanceTest and VehicleServiceTest for consistency
2026-06-03 20:59:52 +03:00
dependabot[bot] 5ff41687df build(deps): bump org.apache.commons:commons-lang3 in /double-buffer (#3304)
Bumps org.apache.commons:commons-lang3 from 3.17.0 to 3.18.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-version: 3.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:48:17 +03:00
dependabot[bot] 51b03f1137 build(deps): bump ch.qos.logback:logback-core in /subclass-sandbox (#3380)
Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.5.18 to 1.5.19.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.18...v_1.5.19)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-version: 1.5.19
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:43:15 +03:00
dependabot[bot] 4215c86de0 build(deps-dev): bump org.assertj:assertj-core in /bloc (#3426)
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.7.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:42:07 +03:00
dependabot[bot] f5bd0380d5 build(deps-dev): bump org.apache.maven.plugins:maven-war-plugin (#3431)
Bumps [org.apache.maven.plugins:maven-war-plugin](https://github.com/apache/maven-war-plugin) from 3.4.0 to 3.5.1.
- [Release notes](https://github.com/apache/maven-war-plugin/releases)
- [Commits](https://github.com/apache/maven-war-plugin/compare/maven-war-plugin-3.4.0...maven-war-plugin-3.5.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-war-plugin
  dependency-version: 3.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:40:39 +03:00
allcontributors[bot] a0a10b300b docs: add orbisai0security as a contributor for security (#3507)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-03 20:33:35 +03:00
OrbisAI SecurityandUbuntu 2c02289711 fix: V-001 security vulnerability (#3461)
Automated security fix generated by Orbis Security AI

Co-authored-by: Ubuntu <ubuntu@ip-172-31-32-15.us-west-2.compute.internal>
2026-06-03 20:32:57 +03:00
allcontributors[bot] 394aff8ac6 docs: add sieberss as a contributor for translation (#3506)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-03 20:21:02 +03:00
Stefan Siebers cd4753a660 docs: Improve existing German READMEs and add new ones (#3444)
* docs: reformulate text, add links to further languages and paragraph about the book in central README.md
(#2275)

* docs: shorten text for link to payhip
(#2275)

* docs: reformulate german translation for Abstract Document
(#2275)

* Create folder for Abstract Factory

* Create README.md for Abstract Document

* Translate README.md for Abstract Document to German

* correct typo

* docs: add class diagrams for all patterns
(#2275)

* docs: add missing words in abstract-factory.README.md
(#2275)

* docs: Create German README.md for Active Object

#2275
first text part

* docs: finish German translation of README.md for Active Object

* docs: translate actor-model into German

#2295

* docs: add German translation for Acyclic Visitor

#2295

* correct typo

* docs: Translate Singleton into German

#2295

* docs: Translate Prototype into German

#2295

* Delete localization/de/Abstract Factory

* insert line break

* correct typos in actor-model

* realignment of java code block

* docs: translate Adapter Pattern into German

#2295

* Update README.md

change 'lizenz' to 'Lizenz' in badge

* Update README.md

translate 'benefits' and 'trade-offs'

* remove additional bullet point after line break

* correct indentation

* docs: translate Dependency Injection into German

#2295

* docs: translate Bridge into German

#2295

* correct typos

* docs: Translate Builder into German

* delete english text

* little changes in formulation

* Update README.md

change link to image

* docs: remove spanish files
(#2275)

* docs: remove non-translated patterns
(#2275)

* Create README.md

docs: Translate Step Builder into German
#2275

* Create README.md

docs: Translate Fluent Interface into German
#2275

* eager is eifrig, not gierig

* Translate Chain of Responsibility into German

#2275
2026-06-03 20:20:05 +03:00
dependabot[bot] bdc008fb78 build(deps): bump com.fasterxml.jackson.core:jackson-core (#3502)
Bumps [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) from 2.19.0 to 2.21.4.
- [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.19.0...jackson-core-2.21.4)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-core
  dependency-version: 2.21.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:15:59 +03:00
allcontributors[bot] 93668590e5 docs: add Dhiraj-birajdar as a contributor for doc (#3499)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-02 21:02:01 +03:00
Dhiraj BirajdarandDhiraj-Birajdar a9ce57c4d0 fix: Fix broken CQRS link in Saga README (#3409)
Co-authored-by: Dhiraj-Birajdar <dhirajbirajdarcs@gmail.com>
2026-06-02 21:01:17 +03:00
allcontributors[bot] f8d4dd8506 docs: add QuantumRange as a contributor for doc (#3498)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-01 21:28:22 +03:00
QuantumRange 2bfd3e190d fix: external spring guide link changed (#3394) 2026-06-01 21:27:43 +03:00
allcontributors[bot] b0bd9e6593 docs: add CMD137 as a contributor for code (#3497)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-06-01 21:13:49 +03:00
CMD137 e8595393d6 feat: Add Thread-Specific Storage design pattern (#3422)
* Add Thread-Specific Storage design pattern

* fixed:pom.xml
Run 'mvn spotless:apply' to fix these violations.

* fix: address Sonar security hotspots for Random usage and e.printStackTrace()

* fixed:
Run 'mvn spotless:apply'(remove unused import)

* fixed:
Refactor Thread-Specific Storage pattern implementation to address Sonar issues: remove unnecessary instantiation, add private constructor, use diamond operator, and update tests.

* fixed:
update uml and README.md

* Trigger CI rerun
2026-06-01 21:11:47 +03:00
allcontributors[bot] 48ba55a8a6 docs: add HY-love-sleep as a contributor for code (#3495)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 18:18:10 +03:00
HY-love-sleep b93a202d60 feat: Refactor Callback pattern to support asynchronous callbacks (#3274)
* refactor: Refactor Task execution to support asynchronous callbacks

* fix: CallbackTest

* chore: code style

* feat: display synchronous and asynchronous callbacks
2026-05-31 18:17:23 +03:00
allcontributors[bot] 9e65023a9f docs: add devch37 as a contributor for code (#3494)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 18:03:44 +03:00
devch37andbehappyleee 9f0b7ae81d feat: add override annotation (#3306)
Co-authored-by: behappyleee <72903623+behappyleee@users.noreply.github.com>
2026-05-31 18:03:03 +03:00
allcontributors[bot] f93514af39 docs: add quantdevv as a contributor for code (#3493)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:55:39 +03:00
Aditya Kumawat 318130c486 #2673: feat: Microservice pattern: Polling publisher #3243 (#3292)
* added new microservice pattern polling-publisher, created folder structure, README.md & App.java

* added two service polling service & publisher service to handle two different operation & prepared initial skeleton

* polling-publisher service implementation

* fixed checkstyle errors

* subscriber-service implementation

* added entry point for subscriber service & unit test-case

* added unit test-case for DataSourceService class

* resolved dependency & structured classes

* implemented Data repository, DataSource Service & added unittest-case & implemented polling-service microservice

* implemented subscriber microservice using kafkaConsumer

* Fixed Kafka bug & added a controller to send message using API

* Fixed subscriber-service & updated pom.xml

* Fixed topic name

* added new listener for other topic

* updated all README.md

* added description on both application.yaml file

* added scope before class definition in Test classes & removed duplicate dependency from pom.xml & added required one

* added polling-publisher module in pom.xml

* Updated README.md file based on new template

* improved the README.md file & fixed it's format

* deleted unnecessary README.md files

* fixed the printing statements & used log function

* used logger to print statement

* addressed review comment: removed unnecessary dependency

* addressed review comment
2026-05-31 17:54:58 +03:00
allcontributors[bot] f7ad42dc05 docs: add avivdon as a contributor for security (#3492)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:28:52 +03:00
allcontributors[bot] 5a0440b696 docs: add jackmisbach as a contributor for security (#3491)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:28:18 +03:00
allcontributors[bot] 546d9a5bae docs: add dagecko as a contributor for security (#3490)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:27:29 +03:00
allcontributors[bot] 213a4a5ee7 docs: add north-echo as a contributor for security (#3489)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:26:42 +03:00
allcontributors[bot] 83887271ae docs: add nilsreichardt as a contributor for security (#3488)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-31 17:25:58 +03:00
Ilkka Seppälä e4384ef7d2 fix: Update PR builder not to execute Sonar analysis (#3486) 2026-05-31 08:03:33 +03:00
iluwatar d24dd22bb9 feat: Add AGENTS.md 2026-05-03 18:11:17 +03:00
allcontributors[bot] 2d39fe7ed8 docs: add Olexandr88 as a contributor for doc (#3440)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-02-22 20:17:34 +02:00
Olexandr88andIlkka Seppälä b8f819988f docs: Update README.md (#3298)
Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2026-02-22 20:16:56 +02:00
allcontributors[bot] ce5154effc docs: add skamble2 as a contributor for code (#3437)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-02-22 20:15:55 +02:00
Soham KambleandIlkka Seppälä 265e3d0bda feat: Rate Limiting Pattern #2973 (#3291)
* need to fix one test case shouldGraduallyIncreaseLimitWhenHealthy failing for AdaptiveRateLimiter.java

* Added Class Diagram and Flow Diagrams for Adaptive, Fixed Window and Token Bucket Rate Limiter

* Updated README.md. All test case passed. Updated with Google Java Guidelines

* Updated parent pom #2973

* Updated parent pom #2973

* fixed shouldResetCounterAfterWindow() test #2973

* formatting fixed #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* added test coverage for app.java and fixed random to be thread safe #2973

* fixed random to be thread safe #2973

* fixed random to be thread safe #2973

* fixed random to be thread safe #2973

* fixed spacing in pom.xml #2973

---------

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2026-02-22 20:15:11 +02:00
e5LAandIlkka Seppälä a3fcc63167 feat: Implement View Helper pattern (#3278)
* #1263 - creating new module for view-helper

* #1263 - creating Product and ProductViewModel

* #1263 - creating interfaces for View and ViewHelper

* #1263 - creating ProductViewHelper and its test

* #1263 - creating Console View for Product

* #1263 - creating ProductController

* #1263 - creating main App and its test

* #1263 - adding license header

* #1263 - keeping module in alphabetical order

* #1263 - creating README and diagrams

* #1263 apply spotless

---------

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2026-02-08 21:44:17 +02:00
allcontributors[bot] dc3425dd0e docs: add letdtcode as a contributor for code (#3429)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-02-08 21:28:19 +02:00
allcontributors[bot] 7c12643b27 docs: add naman-sriv as a contributor for code (#3428)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-02-08 21:27:30 +02:00
Maziyar Geramiandkeivanshir 4caa0bfdbc docs: Persian translation: Abstract Factory (#3280)
* Persian Translation: Add persian translation to abstract-document

* Persian Translation: Add abstract-document.png to abstract-document folder

* Persian Translation: Add codes to README.md of abstract-document

* Persian Translation: some improvements in abstract-document

* Persian Translation: Add refrence links in abstract-document

* Persian Translation: add rtl tag in abstract-document

* -added persian translation of factory pattern

* -renamed file

* -changed wikipedia definition

* -fixed table problem

* active-object : translate

* active-object: improve when to use

* active-object: improve when to use

* active-object: improve bullets (test)

* active-object: improve bullets (test)

* active-object: improve bullets (test)

* active-object: improve bullets (test)

* active-object: improve bullets

* active-object: Fix all bullets

* -fixed problems in bullet alignments

* Update README.md

-fixed alignment in bullets

* Update README.md

-changed tags to English

* -added persian translation of factory pattern

* -renamed file

* -changed wikipedia definition

* -fixed table problem

* -fixed problems in bullet alignments

* Update README.md

-fixed alignment in bullets

* Update README.md

-changed tags to English

* abstract-factory: added persian translation

* abstract-factory: fixed bullets problems

* "Minor improvement

Co-authored-by: keivanshir <keivan.shir.74@gmail.com>"

---------

Co-authored-by: Seyyed Keivan Shirkoubian <keivan.shir.74@gmail.com>
2026-02-08 21:25:16 +02:00
Thanh Nguyen Duc f92e6829c3 feat: Implement DAO Factory pattern (#3282)
* feat: implement dao factory

* feat: done implement dao factory

* test: Add unit test dao-factory

* doc: add document for dao-factory

* fix: h2 inmemory database to pass CI

* fix: unit test pipeline

* add license

* fix SonarQube

* add pom.xml

* fix: throw exception + refactor
2026-02-08 21:11:47 +02:00
dependabot[bot] fecf7e7a82 build(deps-dev): bump io.projectreactor:reactor-test (#3377)
Bumps [io.projectreactor:reactor-test](https://github.com/reactor/reactor-core) from 3.8.0-M1 to 3.8.0-RC1.
- [Release notes](https://github.com/reactor/reactor-core/releases)
- [Commits](https://github.com/reactor/reactor-core/compare/v3.8.0-M1...v3.8.0-RC1)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-test
  dependency-version: 3.8.0-RC1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-08 21:11:22 +02:00
Naman Srivastava 812e4cd6a2 feat: Microservices pattern : Self-Registration (#3245)
* Services and information

* README.md updated

* unit tests added for controllers

* pom.xml, clean up, java version update and slf4j logging added

* pom files updated

* Removed children maven wrappers

* Microservice-self-registration added to pom file

* POM file updated for self-registration

* pom file updated

* Update pom.xml

correction to mudule name

* SonarQube comments addressed

* sonarqube comments on coverage addressed

* Code coverage for custom health checks

* changes for unit tests

* changes for unit tests

* changes to for code coverage improvement
2026-02-08 20:40:07 +02:00
dependabot[bot] 3ba2560e09 build(deps-dev): bump org.htmlunit:htmlunit from 4.7.0 to 4.17.0 (#3334)
Bumps [org.htmlunit:htmlunit](https://github.com/HtmlUnit/htmlunit) from 4.7.0 to 4.17.0.
- [Release notes](https://github.com/HtmlUnit/htmlunit/releases)
- [Commits](https://github.com/HtmlUnit/htmlunit/compare/4.7.0...4.17.0)

---
updated-dependencies:
- dependency-name: org.htmlunit:htmlunit
  dependency-version: 4.17.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-08 20:37:15 +02:00
ilkka.seppala 08651d0a00 chore: Update presubmit.ai configuration 2026-01-17 21:45:49 +02:00
allcontributors[bot] 0ab60190ea docs: add ronodhirSoumik as a contributor for doc (#3418)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-01-17 21:31:13 +02:00
Soumik Sarker 98d3601b1e docs: Formatted spacing and updated documentation (#3290)
* Refactored redundant spacing in pom.xml

* Updated documentation of remove subscriber in publish-subscribe model
2026-01-17 21:28:23 +02:00
425 changed files with 28352 additions and 234 deletions

No files matched your search

+280 -1
View File
@@ -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,
+5
View File
@@ -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 }}
+8 -19
View File
@@ -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 }}
+21 -28
View File
@@ -1,35 +1,28 @@
name: Presubmit.ai name: Presubmit.ai
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
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:
review: review:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check required secrets - name: Check required secrets
run: | run: |
if [ -z "${{ secrets.LLM_API_KEY }}" ]; then if [ -z "${{ secrets.LLM_API_KEY }}" ]; then
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 env:
uses: actions/checkout@v3 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
ref: ${{ github.event.pull_request.head.sha }} LLM_MODEL: "gpt-5-nano"
- name: Run AI Reviewer
uses: presubmit/ai-reviewer@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: "gemini-1.5-flash"
+51
View File
@@ -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.
+66 -4
View File
@@ -1,17 +1,18 @@
# Design Patterns Implemented in Java # Design Patterns Implemented in Java
![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) [![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg)](https://github.com/iluwatar/java-design-patterns/actions/workflows/maven-ci.yml)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-387-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-418-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/> <br/>
Read in different language : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**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>
+39
View File
@@ -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

+39
View File
@@ -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

+85
View File
@@ -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

+148
View File
@@ -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
![Backends For Frontends class diagram](./etc/backends-for-frontends.png)
## 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
+67
View File
@@ -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;
@@ -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) {}
@@ -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);
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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"));
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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());
}
}
+1 -1
View File
@@ -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
View File
@@ -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");
} }
} }
+1 -1
View File
@@ -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
View File
@@ -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>
+360
View File
@@ -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
![Data Access Object Factory class diagram](./etc/dao-factory.png "Data Access Object Factory 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

+74
View File
@@ -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
+76
View File
@@ -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();
}
}
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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>
+2 -2
View File
@@ -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>
+6 -7
View File
@@ -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
+114
View File
@@ -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.
+70
View File
@@ -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());
}
}
}
+161
View File
@@ -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)
+50
View File
@@ -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));
}
}
+1 -1
View File
@@ -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