docs: Content SEO updates (#2990)

* update yaml frontmatter format

* update abstract document

* update abstract factory

* use the new pattern template

* acyclic visitor seo

* adapter seo

* ambassador seo

* acl seo

* aaa seo

* async method invocation seo

* balking seo

* bridge seo

* builder seo

* business delegate and bytecode seo

* caching seo

* callback seo

* chain seo

* update headings

* circuit breaker seo

* client session + collecting parameter seo

* collection pipeline seo

* combinator SEO

* command seo

* cqrs seo

* commander seo

* component seo

* composite seo

* composite entity seo

* composite view seo

* context object seo

* converter seo

* crtp seo

* currying seo

* dao seo

* data bus seo

* data locality seo

* data mapper seo

* dto seo

* decorator seo

* delegation seo

* di seo

* dirty flag seo

* domain model seo

* double buffer seo

* double checked locking seo

* double dispatch seo

* dynamic proxy seo

* event aggregator seo

* event-based asynchronous seo

* eda seo

* event queue seo

* event sourcing seo

* execute around seo

* extension objects seo

* facade seo

* factory seo

* factory kit seo

* factory method seo

* fanout/fanin seo

* feature toggle seo

* filterer seo

* fluent interface seo

* flux seo

* flyweight seo

* front controller seo

* function composition seo

* game loop seo

* gateway seo

* guarded suspension seo

* half-sync/half-async seo

* health check seo

* hexagonal seo

* identity map seo

* intercepting filter seo

* interpreter seo

* iterator seo

* layers seo

* lazy loading seo

* leader election seo

* leader/followers seo

* lockable object seo

* rename and add seo for marker interface

* master-worker seo

* mediator seo

* memento seo

* metadata mapping seo

* microservice aggregator seo

* api gw seo

* microservices log aggregration seo

* mvc seo

* mvi seo

* mvp seo

* mvvm seo

* monad seo

* monitor seo

* monostate seo

* multiton seo

* mute idiom seo

* naked objects & notification seo

* null object seo

* object mother seo

* object pool seo

* observer seo

* optimistic locking seo

* page controller seo

* page object seo

* parameter object seo

* partial response seo

* pipeline seo

* poison pill seo

* presentation model seo

* private class data seo

* producer-consumer seo

* promise seo

* property seo

* prototype seo

* proxy seo

* queue-based load leveling seo

* reactor seo

* registry seo

* repository seo

* RAII seo

* retry seo

* role object seo

* saga seo

* separated interface seo

* serialized entity seo

* serialized lob seo

* servant seo

* server session seo

* service layer seo

* service locator seo

* service to worker seo

* sharding seo

* single table inheritance seo

* singleton seo

* spatial partition seo

* special case seo

* specification seo

* state seo

* step builder seo

* strangler seo

* strategy seo

* subclass sandbox seo

* table module seo

* template method seo

* throttling seo

* tolerant reader seo

* trampoline seo

* transaction script seo

* twin seo

* type object seo

* unit of work seo

* update method seo

* value object seo

* version number seo

* virtual proxy seo

* visitor seo

* seo enhancements

* seo improvements

* SEO enhancements

* SEO improvements

* SEO additions

* SEO improvements

* more SEO improvements

* rename hexagonal + SEO improvements

* SEO improvements

* more SEO stuff

* SEO improvements

* SEO optimizations

* SEO enhancements

* enchance SEO

* improve SEO

* SEO improvements

* update headers
This commit is contained in:
Ilkka Seppälä
2024-06-08 19:54:44 +03:00
committed by GitHub
parent cb946c0cdc
commit 6cd2d0353a
219 changed files with 3308 additions and 2819 deletions
+210
View File
@@ -0,0 +1,210 @@
---
title: "Hexagonal Architecture Pattern in Java: Decoupling Core Logic for Enhanced Flexibility"
shortTitle: Hexagonal Architecture
description: "Explore the Hexagonal Architecture pattern in Java. Learn how it decouples core logic from external interfaces, enhances maintainability, and improves testability with practical examples."
category: Architectural
language: en
tag:
- Decoupling
- Layered architecture
---
## Also known as
* Ports and Adapters
## Intent of Hexagonal Architecture Design Pattern
Hexagonal Architecture, also known as Ports and Adapters, is a design pattern in Java that promotes decoupling of core business logic from external interfaces like databases and user interfaces. This architectural approach enhances maintainability and testability of software systems.
## Detailed Explanation of Hexagonal Architecture Pattern with Real-World Examples
Real-world example
> In online banking systems, Hexagonal Architecture allows core banking logic to remain unaffected by changes in user interfaces or third-party services. This decoupling ensures the system's maintainability and flexibility. In such systems, the core banking logic (like processing transactions, managing accounts, and calculating interest) represents the application's core. This core is then surrounded by various adapters that allow the system to interact with different external interfaces without affecting the business logic. For instance, customers might access their accounts through a web interface, a mobile app, or even through ATM services. Meanwhile, the banking system also needs to interface with external services for credit checks, fraud detection, and interbank transactions. Each of these interfaces interacts with the core banking logic through specific adapters designed to translate the external calls to and from the application's internal APIs. This setup allows the bank to modify or extend its external interfaces without having to alter the core business logic, enhancing flexibility and maintainability.
In plain words
> Hexagonal Architecture organizes an application into a central core of business logic surrounded by ports and adapters that manage interactions with external systems like user interfaces and databases, allowing the core to remain independent of external concerns.
Wikipedia says
> The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.
## Programmatic Example of Hexagonal Architecture Pattern in Java
The Hexagonal Architecture, also known as Ports and Adapters, is a design pattern that aims to create a loosely coupled application where the core business logic is isolated from external interfaces like databases, user interfaces, or third-party services. This allows the core application to be independent and easily testable.
The Java code example below illustrates how Hexagonal Architecture isolates core business logic using dependency injection, making the application highly testable and independent from external components.
In the provided code, we can see an example of the Hexagonal Architecture pattern in the `App` class and the use of Google's Guice for dependency injection.
The `App` class is the entry point of the application. It creates an instance of `LotteryAdministration` and `LotteryService` through dependency injection and uses them to handle various tasks.
```java
public class App {
public static void main(String[] args) {
var injector = Guice.createInjector(new LotteryTestingModule());
// start new lottery round
var administration = injector.getInstance(LotteryAdministration.class);
administration.resetLottery();
// submit some lottery tickets
var service = injector.getInstance(LotteryService.class);
SampleData.submitTickets(service, 20);
// perform lottery
administration.performLottery();
}
}
```
The `LotteryAdministration` class is responsible for managing the lottery rounds. It has methods to start a new round, perform the lottery, and reset the lottery.
```java
public class LotteryAdministration {
private final LotteryTicketRepository repository;
private final LotteryEventLog notifications;
private final WireTransfers wireTransfers;
@Inject
public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications,
WireTransfers wireTransfers) {
this.repository = repository;
this.notifications = notifications;
this.wireTransfers = wireTransfers;
}
public Map<LotteryTicketId, LotteryTicket> getAllSubmittedTickets() {
return repository.findAll();
}
public LotteryNumbers performLottery() {
// Implementation details...
}
public void resetLottery() {
repository.deleteAll();
}
}
```
The `LotteryService` class is responsible for managing the lottery tickets. It has methods to submit a ticket, check a ticket's status, and get the winning ticket.
```java
public class LotteryService {
private final LotteryTicketRepository repository;
private final LotteryEventLog notifications;
private final WireTransfers wireTransfers;
@Inject
public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications,
WireTransfers wireTransfers) {
this.repository = repository;
this.notifications = notifications;
this.wireTransfers = wireTransfers;
}
public Optional<LotteryTicketId> submitTicket(LotteryTicket ticket) {
// Implementation details...
}
public LotteryTicketCheckResult checkTicketForPrize(
LotteryTicketId id,
LotteryNumbers winningNumbers
) {
// Implementation details...
}
}
```
Running the main function of App class produces the following output:
```
11:06:58.357 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits.
11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for arnold@google.com was submitted. Bank account 114-988 was charged for 3 credits.
11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ollie@google.com was submitted. Bank account 190-045 was charged for 3 credits.
11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for peter@google.com was submitted. Bank account 335-886 was charged for 3 credits.
11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ray@google.com was submitted. Bank account 843-073 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lisa@google.com was submitted. Bank account 024-653 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for harriet@google.com was submitted. Bank account 842-404 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com was submitted. Bank account 734-853 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lars@google.com was submitted. Bank account 746-936 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for tyron@google.com was submitted. Bank account 310-992 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for xavier@google.com was submitted. Bank account 143-947 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for kevin@google.com was submitted. Bank account 453-936 was charged for 3 credits.
11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for yngwie@google.com was submitted. Bank account 241-465 was charged for 3 credits.
11:06:58.361 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits.
11:06:58.361 [main] ERROR com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com could not be submitted because the credit transfer of 3 credits failed.
11:06:58.362 [main] ERROR com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com could not be submitted because the credit transfer of 3 credits failed.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for mary@google.com was submitted. Bank account 234-987 was charged for 3 credits.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for edwin@google.com was submitted. Bank account 895-345 was charged for 3 credits.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for jacob@google.com was submitted. Bank account 444-766 was charged for 3 credits.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ollie@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for jacob@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for peter@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ray@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lisa@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for harriet@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lars@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for tyron@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for xavier@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for kevin@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for yngwie@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for mary@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for arnold@google.com was checked and unfortunately did not win this time.
11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for edwin@google.com was checked and unfortunately did not win this time.
```
In this example, the `LotteryAdministration` and `LotteryService` classes are the core of the application. They interact with external interfaces like `LotteryTicketRepository`, `LotteryEventLog`, and `WireTransfers` through dependency injection, keeping the core business logic decoupled from external concerns. This is a basic example of the Hexagonal Architecture pattern, where the core application is at the center of input/output systems.
## Detailed Explanation of Hexagonal Architecture Pattern with Real-World Examples
![Hexagonal Architecture class diagram](./etc/hexagonal.png)
## When to Use the Hexagonal Architecture Pattern in Java
Hexagonal Architecture is particularly beneficial in scenarios:
* The application needs to interact with multiple external systems.
* There is a requirement for high testability and maintainability.
* The application should remain unaffected by changes in external interfaces.
## Real-World Applications of Hexagonal Architecure Pattern in Java
* Implemented extensively within enterprise applications that leverage frameworks like Spring.
* Used in microservices architectures to maintain clear boundaries and protocols between services.
* Adopted in systems that require integration with various databases or external APIs without impacting the business logic.
## Benefits and Trade-offs of Hexagonal Architecture Pattern
Benefits:
* Improved Testability: Allows the core functionality to be tested independently of external components.
* Flexibility: Facilitates the addition or replacement of components that interact with the application without modifying the core business logic.
* Maintainability: Reduces dependencies on external interfaces, simplifying upgrades and maintenance.
Trade-offs:
* Complexity: Introduces more abstractions and layers, which can complicate the system design and understanding.
* Overhead: Might be an over-engineering for simple applications, where simpler architectural patterns could suffice.
## Related Java Design Patterns
* [Layered Architecture](https://java-design-patterns.com/patterns/layers/): Shares the concept of organizing code into responsibilities; however, Hexagonal emphasizes port-based interaction with external elements.
* Microservices: Often used in conjunction with Hexagonal Architecture to define clear boundaries and protocols between services.
## References and Credits
* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB)
* [Building Microservices](https://amzn.to/3UACtrU)
Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

+280
View File
@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.10" icons="true" automaticImage="PNG" always-add-relationships="false"
generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true"
router="FAN">
<class id="1" language="java" name="com.iluwatar.hexagonal.eventlog.StdOutEventLog" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/notifications/StdOutNotifications.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="167" width="235" x="731" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.hexagonal.banking.InMemoryBank" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="149" width="217" x="1006" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.hexagonal.domain.LotterySystem" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotterySystemImpl.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="167" width="421" x="1263" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.hexagonal.domain.LotteryTicket" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="167" width="278" x="1568" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.hexagonal.database.InMemoryTicketRepository" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="149" width="265" x="1724" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="6" language="java" name="com.iluwatar.hexagonal.banking.WireTransfers" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="113" width="217" x="1006" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="7" language="java" name="com.iluwatar.hexagonal.domain.LotteryService" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="113" width="421" x="2029" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="8" language="java" name="com.iluwatar.hexagonal.database.LotteryTicketRepository" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="131" width="265" x="1263" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<interface id="9" language="java" name="com.iluwatar.hexagonal.domain.LotterySystem" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotterySystem.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="149" width="421" x="1886" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="10" language="java" name="com.iluwatar.hexagonal.domain.LotteryNumbers" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="275" width="209" x="1568" y="554"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="11" language="java" name="com.iluwatar.hexagonal.domain.LotteryAdministration"
project="hexagonal" file="/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/LotteryAdministration.java"
binary="false" corner="BOTTOM_RIGHT">
<position height="113" width="320" x="2808" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="12" language="java" name="com.iluwatar.hexagonal.domain.LotteryNumbers.RandomNumberGenerator"
project="hexagonal" file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="113" width="189" x="1568" y="869"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="13" language="java" name="com.iluwatar.hexagonal.domain.LotteryAdministration" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="131" width="320" x="2490" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="14" language="java" name="com.iluwatar.hexagonal.domain.PlayerDetails" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="239" width="222" x="1019" y="554"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="15" language="java" name="com.iluwatar.hexagonal.domain.LotteryService" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="95" width="421" x="2347" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="16" language="java" name="com.iluwatar.hexagonal.App" project="hexagonal"
file="/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="131" width="221" x="470" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="17" language="java" name="com.iluwatar.hexagonal.eventlog.LotteryEventLog"
project="hexagonal" file="/hexagonal/src/main/java/com/iluwatar/hexagonal/notifications/LotteryNotifications.java"
binary="false" corner="BOTTOM_RIGHT">
<position height="149" width="235" x="731" y="347"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<association id="18">
<end type="SOURCE" refId="16" navigable="false">
<attribute id="19" name="PLAYERS"/>
<multiplicity id="20" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="14" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="21">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="22" name="wireTransfers"/>
<multiplicity id="23" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="24">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="8"/>
</realization>
<nesting id="25">
<end type="SOURCE" refId="10"/>
<end type="TARGET" refId="12"/>
</nesting>
<association id="26">
<end type="SOURCE" refId="5" navigable="false">
<attribute id="27" name="tickets"/>
<multiplicity id="28" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="29">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="17"/>
</realization>
<realization id="30">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="9"/>
</realization>
<realization id="31">
<end type="SOURCE" refId="7"/>
<end type="TARGET" refId="15"/>
</realization>
<association id="32">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="33" name="lotteryNumbers"/>
<multiplicity id="34" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="10" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="35">
<end type="SOURCE" refId="13"/>
<end type="TARGET" refId="11"/>
</realization>
<association id="36">
<end type="SOURCE" refId="13" navigable="false">
<attribute id="37" name="lotterySystem"/>
<multiplicity id="38" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="9" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="39">
<end type="SOURCE" refId="7" navigable="false">
<attribute id="40" name="lotterySystem"/>
<multiplicity id="41" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="9" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="42">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="43" name="repository"/>
<multiplicity id="44" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="8" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="45">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="6"/>
</realization>
<association id="46">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="47" name="notifications"/>
<multiplicity id="48" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="17" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="49">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="50" name="playerDetails"/>
<multiplicity id="51" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="14" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>
@@ -0,0 +1,305 @@
@startuml
package com.iluwatar.hexagonal.sampledata {
class SampleData {
- PLAYERS : List<PlayerDetails> {static}
- RANDOM : Random {static}
+ SampleData()
- getRandomPlayerDetails() : PlayerDetails {static}
+ submitTickets(lotteryService : LotteryService, numTickets : int) {static}
}
}
package com.iluwatar.hexagonal.service {
class ConsoleLottery {
- LOGGER : Logger {static}
+ ConsoleLottery()
+ main(args : String[]) {static}
- printMainMenu() {static}
- readString(scanner : Scanner) : String {static}
}
interface LotteryConsoleService {
+ addFundsToLotteryAccount(WireTransfers, Scanner) {abstract}
+ checkTicket(LotteryService, Scanner) {abstract}
+ queryLotteryAccountFunds(WireTransfers, Scanner) {abstract}
+ submitTicket(LotteryService, Scanner) {abstract}
}
class LotteryConsoleServiceImpl {
- logger : Logger
+ LotteryConsoleServiceImpl(logger : Logger)
+ addFundsToLotteryAccount(bank : WireTransfers, scanner : Scanner)
+ checkTicket(service : LotteryService, scanner : Scanner)
+ queryLotteryAccountFunds(bank : WireTransfers, scanner : Scanner)
- readString(scanner : Scanner) : String
+ submitTicket(service : LotteryService, scanner : Scanner)
}
}
package com.iluwatar.hexagonal.mongo {
class MongoConnectionPropertiesLoader {
- DEFAULT_HOST : String {static}
- DEFAULT_PORT : int {static}
+ MongoConnectionPropertiesLoader()
+ load() {static}
}
}
package com.iluwatar.hexagonal.domain {
class LotteryAdministration {
- notifications : LotteryEventLog
- repository : LotteryTicketRepository
- wireTransfers : WireTransfers
+ LotteryAdministration(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers)
+ getAllSubmittedTickets() : Map<LotteryTicketId, LotteryTicket>
+ performLottery() : LotteryNumbers
+ resetLottery()
}
class LotteryConstants {
+ PLAYER_MAX_BALANCE : int {static}
+ PRIZE_AMOUNT : int {static}
+ SERVICE_BANK_ACCOUNT : String {static}
+ SERVICE_BANK_ACCOUNT_BALANCE : int {static}
+ TICKET_PRIZE : int {static}
- LotteryConstants()
}
class LotteryNumbers {
+ MAX_NUMBER : int {static}
+ MIN_NUMBER : int {static}
+ NUM_NUMBERS : int {static}
- numbers : Set<Integer>
- LotteryNumbers()
- LotteryNumbers(givenNumbers : Set<Integer>)
+ create(givenNumbers : Set<Integer>) : LotteryNumbers {static}
+ createRandom() : LotteryNumbers {static}
+ equals(obj : Object) : boolean
- generateRandomNumbers()
+ getNumbers() : Set<Integer>
+ getNumbersAsString() : String
+ hashCode() : int
+ toString() : String
}
-class RandomNumberGenerator {
- randomIterator : OfInt
+ RandomNumberGenerator(min : int, max : int)
+ nextInt() : int
}
class LotteryService {
- notifications : LotteryEventLog
- repository : LotteryTicketRepository
- wireTransfers : WireTransfers
+ LotteryService(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers)
+ checkTicketForPrize(id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult
+ submitTicket(ticket : LotteryTicket) : Optional<LotteryTicketId>
}
class LotteryTicket {
- id : LotteryTicketId
- lotteryNumbers : LotteryNumbers
- playerDetails : PlayerDetails
+ LotteryTicket(id : LotteryTicketId, details : PlayerDetails, numbers : LotteryNumbers)
+ equals(obj : Object) : boolean
+ getId() : LotteryTicketId
+ getNumbers() : LotteryNumbers
+ getPlayerDetails() : PlayerDetails
+ hashCode() : int
+ setId(id : LotteryTicketId)
+ toString() : String
}
class LotteryTicketCheckResult {
- checkResult : CheckResult
- prizeAmount : int
+ LotteryTicketCheckResult(result : CheckResult)
+ LotteryTicketCheckResult(result : CheckResult, amount : int)
+ equals(obj : Object) : boolean
+ getPrizeAmount() : int
+ getResult() : CheckResult
+ hashCode() : int
}
enum CheckResult {
+ NO_PRIZE {static}
+ TICKET_NOT_SUBMITTED {static}
+ WIN_PRIZE {static}
+ valueOf(name : String) : CheckResult {static}
+ values() : CheckResult[] {static}
}
class LotteryTicketId {
- id : int
- numAllocated : AtomicInteger {static}
+ LotteryTicketId()
+ LotteryTicketId(id : int)
+ equals(o : Object) : boolean
+ getId() : int
+ hashCode() : int
+ toString() : String
}
class LotteryUtils {
- LotteryUtils()
+ checkTicketForPrize(repository : LotteryTicketRepository, id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult {static}
}
class PlayerDetails {
- bankAccountNumber : String
- emailAddress : String
- phoneNumber : String
+ PlayerDetails(email : String, bankAccount : String, phone : String)
+ equals(obj : Object) : boolean
+ getBankAccount() : String
+ getEmail() : String
+ getPhoneNumber() : String
+ hashCode() : int
+ toString() : String
}
}
package com.iluwatar.hexagonal.banking {
class InMemoryBank {
- accounts : Map<String, Integer> {static}
+ InMemoryBank()
+ getFunds(bankAccount : String) : int
+ setFunds(bankAccount : String, amount : int)
+ transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean
}
class MongoBank {
- DEFAULT_ACCOUNTS_COLLECTION : String {static}
- DEFAULT_DB : String {static}
- accountsCollection : MongoCollection<Document>
- database : MongoDatabase
- mongoClient : MongoClient
+ MongoBank()
+ MongoBank(dbName : String, accountsCollectionName : String)
+ connect()
+ connect(dbName : String, accountsCollectionName : String)
+ getAccountsCollection() : MongoCollection<Document>
+ getFunds(bankAccount : String) : int
+ getMongoClient() : MongoClient
+ getMongoDatabase() : MongoDatabase
+ setFunds(bankAccount : String, amount : int)
+ transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean
}
interface WireTransfers {
+ getFunds(String) : int {abstract}
+ setFunds(String, int) {abstract}
+ transferFunds(int, String, String) : boolean {abstract}
}
}
package com.iluwatar.hexagonal.database {
class InMemoryTicketRepository {
- tickets : Map<LotteryTicketId, LotteryTicket> {static}
+ InMemoryTicketRepository()
+ deleteAll()
+ findAll() : Map<LotteryTicketId, LotteryTicket>
+ findById(id : LotteryTicketId) : Optional<LotteryTicket>
+ save(ticket : LotteryTicket) : Optional<LotteryTicketId>
}
interface LotteryTicketRepository {
+ deleteAll() {abstract}
+ findAll() : Map<LotteryTicketId, LotteryTicket> {abstract}
+ findById(LotteryTicketId) : Optional<LotteryTicket> {abstract}
+ save(LotteryTicket) : Optional<LotteryTicketId> {abstract}
}
class MongoTicketRepository {
- DEFAULT_COUNTERS_COLLECTION : String {static}
- DEFAULT_DB : String {static}
- DEFAULT_TICKETS_COLLECTION : String {static}
- countersCollection : MongoCollection<Document>
- database : MongoDatabase
- mongoClient : MongoClient
- ticketsCollection : MongoCollection<Document>
+ MongoTicketRepository()
+ MongoTicketRepository(dbName : String, ticketsCollectionName : String, countersCollectionName : String)
+ connect()
+ connect(dbName : String, ticketsCollectionName : String, countersCollectionName : String)
+ deleteAll()
- docToTicket(doc : Document) : LotteryTicket
+ findAll() : Map<LotteryTicketId, LotteryTicket>
+ findById(id : LotteryTicketId) : Optional<LotteryTicket>
+ getCountersCollection() : MongoCollection<Document>
+ getNextId() : int
+ getTicketsCollection() : MongoCollection<Document>
- initCounters()
+ save(ticket : LotteryTicket) : Optional<LotteryTicketId>
}
}
package com.iluwatar.hexagonal {
class App {
+ App()
+ main(args : String[]) {static}
}
}
package com.iluwatar.hexagonal.administration {
class ConsoleAdministration {
- LOGGER : Logger {static}
+ ConsoleAdministration()
+ main(args : String[]) {static}
- printMainMenu() {static}
- readString(scanner : Scanner) : String {static}
}
interface ConsoleAdministrationSrv {
+ getAllSubmittedTickets() {abstract}
+ performLottery() {abstract}
+ resetLottery() {abstract}
}
class ConsoleAdministrationSrvImpl {
- administration : LotteryAdministration
- logger : Logger
+ ConsoleAdministrationSrvImpl(administration : LotteryAdministration, logger : Logger)
+ getAllSubmittedTickets()
+ performLottery()
+ resetLottery()
}
}
package com.iluwatar.hexagonal.eventlog {
interface LotteryEventLog {
+ prizeError(PlayerDetails, int) {abstract}
+ ticketDidNotWin(PlayerDetails) {abstract}
+ ticketSubmitError(PlayerDetails) {abstract}
+ ticketSubmitted(PlayerDetails) {abstract}
+ ticketWon(PlayerDetails, int) {abstract}
}
class MongoEventLog {
- DEFAULT_DB : String {static}
- DEFAULT_EVENTS_COLLECTION : String {static}
- database : MongoDatabase
- eventsCollection : MongoCollection<Document>
- mongoClient : MongoClient
- stdOutEventLog : StdOutEventLog
+ MongoEventLog()
+ MongoEventLog(dbName : String, eventsCollectionName : String)
+ connect()
+ connect(dbName : String, eventsCollectionName : String)
+ getEventsCollection() : MongoCollection<Document>
+ getMongoClient() : MongoClient
+ getMongoDatabase() : MongoDatabase
+ prizeError(details : PlayerDetails, prizeAmount : int)
+ ticketDidNotWin(details : PlayerDetails)
+ ticketSubmitError(details : PlayerDetails)
+ ticketSubmitted(details : PlayerDetails)
+ ticketWon(details : PlayerDetails, prizeAmount : int)
}
class StdOutEventLog {
- LOGGER : Logger {static}
+ StdOutEventLog()
+ prizeError(details : PlayerDetails, prizeAmount : int)
+ ticketDidNotWin(details : PlayerDetails)
+ ticketSubmitError(details : PlayerDetails)
+ ticketSubmitted(details : PlayerDetails)
+ ticketWon(details : PlayerDetails, prizeAmount : int)
}
}
LotteryTicket --> "-playerDetails" PlayerDetails
MongoEventLog --> "-stdOutEventLog" StdOutEventLog
LotteryService --> "-wireTransfers" WireTransfers
LotteryAdministration --> "-notifications" LotteryEventLog
LotteryAdministration --> "-wireTransfers" WireTransfers
LotteryTicket --> "-id" LotteryTicketId
LotteryAdministration --> "-repository" LotteryTicketRepository
LotteryService --> "-notifications" LotteryEventLog
LotteryTicket --> "-lotteryNumbers" LotteryNumbers
SampleData --> "-PLAYERS" PlayerDetails
ConsoleAdministrationSrvImpl --> "-administration" LotteryAdministration
RandomNumberGenerator ..+ LotteryNumbers
LotteryService --> "-repository" LotteryTicketRepository
CheckResult ..+ LotteryTicketCheckResult
LotteryTicketCheckResult --> "-checkResult" CheckResult
ConsoleAdministrationSrvImpl ..|> ConsoleAdministrationSrv
InMemoryBank ..|> WireTransfers
MongoBank ..|> WireTransfers
InMemoryTicketRepository ..|> LotteryTicketRepository
MongoTicketRepository ..|> LotteryTicketRepository
MongoEventLog ..|> LotteryEventLog
StdOutEventLog ..|> LotteryEventLog
LotteryConsoleServiceImpl ..|> LotteryConsoleService
@enduml
+80
View File
@@ -0,0 +1,80 @@
<?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>hexagonal-architecture</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-legacy</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.hexagonal.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,79 @@
/*
* 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.hexagonal;
import com.google.inject.Guice;
import com.iluwatar.hexagonal.domain.LotteryAdministration;
import com.iluwatar.hexagonal.domain.LotteryService;
import com.iluwatar.hexagonal.module.LotteryTestingModule;
import com.iluwatar.hexagonal.sampledata.SampleData;
/**
* Hexagonal Architecture pattern decouples the application core from the services it uses. This
* allows the services to be plugged in and the application will run with or without the services.
*
* <p>The core logic, or business logic, of an application consists of the algorithms that are
* essential to its purpose. They implement the use cases that are the heart of the application.
* When you change them, you change the essence of the application.
*
* <p>The services are not essential. They can be replaced without changing the purpose of the
* application. Examples: database access and other types of storage, user interface components,
* e-mail and other communication components, hardware devices.
*
* <p>This example demonstrates Hexagonal Architecture with a lottery system. The application core
* is separate from the services that drive it and from the services it uses.
*
* <p>The primary ports for the application are console interfaces {@link
* com.iluwatar.hexagonal.administration.ConsoleAdministration} through which the lottery round is
* initiated and run and {@link com.iluwatar.hexagonal.service.ConsoleLottery} that allows players
* to submit lottery tickets for the draw.
*
* <p>The secondary ports that application core uses are{@link
* com.iluwatar.hexagonal.banking.WireTransfers} which is a banking service, {@link
* com.iluwatar.hexagonal.eventlog.LotteryEventLog} that delivers eventlog as lottery events occur
* and {@link com.iluwatar.hexagonal.database.LotteryTicketRepository} that is the storage for the
* lottery tickets.
*/
public class App {
/**
* Program entry point.
*/
public static void main(String[] args) {
var injector = Guice.createInjector(new LotteryTestingModule());
// start new lottery round
var administration = injector.getInstance(LotteryAdministration.class);
administration.resetLottery();
// submit some lottery tickets
var service = injector.getInstance(LotteryService.class);
SampleData.submitTickets(service, 20);
// perform lottery
administration.performLottery();
}
}
@@ -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.hexagonal.administration;
import com.google.inject.Guice;
import com.iluwatar.hexagonal.domain.LotteryAdministration;
import com.iluwatar.hexagonal.domain.LotteryService;
import com.iluwatar.hexagonal.module.LotteryModule;
import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader;
import com.iluwatar.hexagonal.sampledata.SampleData;
import java.util.Scanner;
import lombok.extern.slf4j.Slf4j;
/**
* Console interface for lottery administration.
*/
@Slf4j
public class ConsoleAdministration {
/**
* Program entry point.
*/
public static void main(String[] args) {
MongoConnectionPropertiesLoader.load();
var injector = Guice.createInjector(new LotteryModule());
var administration = injector.getInstance(LotteryAdministration.class);
var service = injector.getInstance(LotteryService.class);
SampleData.submitTickets(service, 20);
var consoleAdministration = new ConsoleAdministrationSrvImpl(administration, LOGGER);
try (var scanner = new Scanner(System.in)) {
var exit = false;
while (!exit) {
printMainMenu();
var cmd = readString(scanner);
if ("1".equals(cmd)) {
consoleAdministration.getAllSubmittedTickets();
} else if ("2".equals(cmd)) {
consoleAdministration.performLottery();
} else if ("3".equals(cmd)) {
consoleAdministration.resetLottery();
} else if ("4".equals(cmd)) {
exit = true;
} else {
LOGGER.info("Unknown command: {}", cmd);
}
}
}
}
private static void printMainMenu() {
LOGGER.info("");
LOGGER.info("### Lottery Administration Console ###");
LOGGER.info("(1) Show all submitted tickets");
LOGGER.info("(2) Perform lottery draw");
LOGGER.info("(3) Reset lottery ticket database");
LOGGER.info("(4) Exit");
}
private static String readString(Scanner scanner) {
LOGGER.info("> ");
return scanner.next();
}
}
@@ -0,0 +1,46 @@
/*
* 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.hexagonal.administration;
/**
* Console interface for lottery administration.
*/
public interface ConsoleAdministrationSrv {
/**
* Get all submitted tickets.
*/
void getAllSubmittedTickets();
/**
* Draw lottery numbers.
*/
void performLottery();
/**
* Begin new lottery round.
*/
void resetLottery();
}
@@ -0,0 +1,63 @@
/*
* 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.hexagonal.administration;
import com.iluwatar.hexagonal.domain.LotteryAdministration;
import org.slf4j.Logger;
/**
* Console implementation for lottery administration.
*/
public class ConsoleAdministrationSrvImpl implements ConsoleAdministrationSrv {
private final LotteryAdministration administration;
private final Logger logger;
/**
* Constructor.
*/
public ConsoleAdministrationSrvImpl(LotteryAdministration administration, Logger logger) {
this.administration = administration;
this.logger = logger;
}
@Override
public void getAllSubmittedTickets() {
administration.getAllSubmittedTickets()
.forEach((k, v) -> logger.info("Key: {}, Value: {}", k, v));
}
@Override
public void performLottery() {
var numbers = administration.performLottery();
logger.info("The winning numbers: {}", numbers.getNumbersAsString());
logger.info("Time to reset the database for next round, eh?");
}
@Override
public void resetLottery() {
administration.resetLottery();
logger.info("The lottery ticket database was cleared.");
}
}
@@ -0,0 +1,63 @@
/*
* 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.hexagonal.banking;
import com.iluwatar.hexagonal.domain.LotteryConstants;
import java.util.HashMap;
import java.util.Map;
/**
* Banking implementation.
*/
public class InMemoryBank implements WireTransfers {
private static final Map<String, Integer> accounts = new HashMap<>();
static {
accounts
.put(LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_BALANCE);
}
@Override
public void setFunds(String bankAccount, int amount) {
accounts.put(bankAccount, amount);
}
@Override
public int getFunds(String bankAccount) {
return accounts.getOrDefault(bankAccount, 0);
}
@Override
public boolean transferFunds(int amount, String sourceAccount, String destinationAccount) {
if (accounts.getOrDefault(sourceAccount, 0) >= amount) {
accounts.put(sourceAccount, accounts.get(sourceAccount) - amount);
accounts.put(destinationAccount, accounts.get(destinationAccount) + amount);
return true;
} else {
return false;
}
}
}
@@ -0,0 +1,116 @@
/*
* 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.hexagonal.banking;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import java.util.ArrayList;
import lombok.Getter;
import org.bson.Document;
/**
* Mongo based banking adapter.
*/
public class MongoBank implements WireTransfers {
private static final String DEFAULT_DB = "lotteryDB";
private static final String DEFAULT_ACCOUNTS_COLLECTION = "accounts";
@Getter
private MongoClient mongoClient;
@Getter
private MongoDatabase database;
@Getter
private MongoCollection<Document> accountsCollection;
/**
* Constructor.
*/
public MongoBank() {
connect();
}
/**
* Constructor accepting parameters.
*/
public MongoBank(String dbName, String accountsCollectionName) {
connect(dbName, accountsCollectionName);
}
/**
* Connect to database with default parameters.
*/
public void connect() {
connect(DEFAULT_DB, DEFAULT_ACCOUNTS_COLLECTION);
}
/**
* Connect to database with given parameters.
*/
public void connect(String dbName, String accountsCollectionName) {
if (mongoClient != null) {
mongoClient.close();
}
mongoClient = new MongoClient(System.getProperty("mongo-host"),
Integer.parseInt(System.getProperty("mongo-port")));
database = mongoClient.getDatabase(dbName);
accountsCollection = database.getCollection(accountsCollectionName);
}
@Override
public void setFunds(String bankAccount, int amount) {
var search = new Document("_id", bankAccount);
var update = new Document("_id", bankAccount).append("funds", amount);
var updateOptions = new UpdateOptions().upsert(true);
accountsCollection.updateOne(search, new Document("$set", update), updateOptions);
}
@Override
public int getFunds(String bankAccount) {
return accountsCollection
.find(new Document("_id", bankAccount))
.limit(1)
.into(new ArrayList<>())
.stream()
.findFirst()
.map(x -> x.getInteger("funds"))
.orElse(0);
}
@Override
public boolean transferFunds(int amount, String sourceAccount, String destinationAccount) {
var sourceFunds = getFunds(sourceAccount);
if (sourceFunds < amount) {
return false;
} else {
var destFunds = getFunds(destinationAccount);
setFunds(sourceAccount, sourceFunds - amount);
setFunds(destinationAccount, destFunds + amount);
return true;
}
}
}
@@ -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.hexagonal.banking;
/**
* Interface to bank accounts.
*/
public interface WireTransfers {
/**
* Set amount of funds for bank account.
*/
void setFunds(String bankAccount, int amount);
/**
* Get amount of funds for bank account.
*/
int getFunds(String bankAccount);
/**
* Transfer funds from one bank account to another.
*/
boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount);
}
@@ -0,0 +1,61 @@
/*
* 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.hexagonal.database;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Mock database for lottery tickets.
*/
public class InMemoryTicketRepository implements LotteryTicketRepository {
private static final Map<LotteryTicketId, LotteryTicket> tickets = new HashMap<>();
@Override
public Optional<LotteryTicket> findById(LotteryTicketId id) {
return Optional.ofNullable(tickets.get(id));
}
@Override
public Optional<LotteryTicketId> save(LotteryTicket ticket) {
var id = new LotteryTicketId();
tickets.put(id, ticket);
return Optional.of(id);
}
@Override
public Map<LotteryTicketId, LotteryTicket> findAll() {
return tickets;
}
@Override
public void deleteAll() {
tickets.clear();
}
}
@@ -0,0 +1,57 @@
/*
* 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.hexagonal.database;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import java.util.Map;
import java.util.Optional;
/**
* Interface for accessing lottery tickets in database.
*/
public interface LotteryTicketRepository {
/**
* Find lottery ticket by id.
*/
Optional<LotteryTicket> findById(LotteryTicketId id);
/**
* Save lottery ticket.
*/
Optional<LotteryTicketId> save(LotteryTicket ticket);
/**
* Get all lottery tickets.
*/
Map<LotteryTicketId, LotteryTicket> findAll();
/**
* Delete all lottery tickets.
*/
void deleteAll();
}
@@ -0,0 +1,166 @@
/*
* 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.hexagonal.database;
import com.iluwatar.hexagonal.domain.LotteryNumbers;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.Getter;
import org.bson.Document;
/**
* Mongo lottery ticket database.
*/
public class MongoTicketRepository implements LotteryTicketRepository {
private static final String DEFAULT_DB = "lotteryDB";
private static final String DEFAULT_TICKETS_COLLECTION = "lotteryTickets";
private static final String DEFAULT_COUNTERS_COLLECTION = "counters";
private static final String TICKET_ID = "ticketId";
private MongoClient mongoClient;
private MongoDatabase database;
@Getter
private MongoCollection<Document> ticketsCollection;
@Getter
private MongoCollection<Document> countersCollection;
/**
* Constructor.
*/
public MongoTicketRepository() {
connect();
}
/**
* Constructor accepting parameters.
*/
public MongoTicketRepository(String dbName, String ticketsCollectionName,
String countersCollectionName) {
connect(dbName, ticketsCollectionName, countersCollectionName);
}
/**
* Connect to database with default parameters.
*/
public void connect() {
connect(DEFAULT_DB, DEFAULT_TICKETS_COLLECTION, DEFAULT_COUNTERS_COLLECTION);
}
/**
* Connect to database with given parameters.
*/
public void connect(String dbName, String ticketsCollectionName,
String countersCollectionName) {
if (mongoClient != null) {
mongoClient.close();
}
mongoClient = new MongoClient(System.getProperty("mongo-host"),
Integer.parseInt(System.getProperty("mongo-port")));
database = mongoClient.getDatabase(dbName);
ticketsCollection = database.getCollection(ticketsCollectionName);
countersCollection = database.getCollection(countersCollectionName);
if (countersCollection.countDocuments() <= 0) {
initCounters();
}
}
private void initCounters() {
var doc = new Document("_id", TICKET_ID).append("seq", 1);
countersCollection.insertOne(doc);
}
/**
* Get next ticket id.
*
* @return next ticket id
*/
public int getNextId() {
var find = new Document("_id", TICKET_ID);
var increase = new Document("seq", 1);
var update = new Document("$inc", increase);
var result = countersCollection.findOneAndUpdate(find, update);
return result.getInteger("seq");
}
@Override
public Optional<LotteryTicket> findById(LotteryTicketId id) {
return ticketsCollection
.find(new Document(TICKET_ID, id.getId()))
.limit(1)
.into(new ArrayList<>())
.stream()
.findFirst()
.map(this::docToTicket);
}
@Override
public Optional<LotteryTicketId> save(LotteryTicket ticket) {
var ticketId = getNextId();
var doc = new Document(TICKET_ID, ticketId);
doc.put("email", ticket.playerDetails().email());
doc.put("bank", ticket.playerDetails().bankAccount());
doc.put("phone", ticket.playerDetails().phoneNumber());
doc.put("numbers", ticket.lotteryNumbers().getNumbersAsString());
ticketsCollection.insertOne(doc);
return Optional.of(new LotteryTicketId(ticketId));
}
@Override
public Map<LotteryTicketId, LotteryTicket> findAll() {
return ticketsCollection
.find(new Document())
.into(new ArrayList<>())
.stream()
.map(this::docToTicket)
.collect(Collectors.toMap(LotteryTicket::id, Function.identity()));
}
@Override
public void deleteAll() {
ticketsCollection.deleteMany(new Document());
}
private LotteryTicket docToTicket(Document doc) {
var playerDetails = new PlayerDetails(doc.getString("email"), doc.getString("bank"),
doc.getString("phone"));
var numbers = Arrays.stream(doc.getString("numbers").split(","))
.map(Integer::parseInt)
.collect(Collectors.toSet());
var lotteryNumbers = LotteryNumbers.create(numbers);
var ticketId = new LotteryTicketId(doc.getInteger(TICKET_ID));
return new LotteryTicket(ticketId, playerDetails, lotteryNumbers);
}
}
@@ -0,0 +1,93 @@
/*
* 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.hexagonal.domain;
import static com.iluwatar.hexagonal.domain.LotteryConstants.PRIZE_AMOUNT;
import static com.iluwatar.hexagonal.domain.LotteryConstants.SERVICE_BANK_ACCOUNT;
import com.google.inject.Inject;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.database.LotteryTicketRepository;
import com.iluwatar.hexagonal.eventlog.LotteryEventLog;
import java.util.Map;
/**
* Lottery administration implementation.
*/
public class LotteryAdministration {
private final LotteryTicketRepository repository;
private final LotteryEventLog notifications;
private final WireTransfers wireTransfers;
/**
* Constructor.
*/
@Inject
public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications,
WireTransfers wireTransfers) {
this.repository = repository;
this.notifications = notifications;
this.wireTransfers = wireTransfers;
}
/**
* Get all the lottery tickets submitted for lottery.
*/
public Map<LotteryTicketId, LotteryTicket> getAllSubmittedTickets() {
return repository.findAll();
}
/**
* Draw lottery numbers.
*/
public LotteryNumbers performLottery() {
var numbers = LotteryNumbers.createRandom();
var tickets = getAllSubmittedTickets();
for (var id : tickets.keySet()) {
var lotteryTicket = tickets.get(id);
var playerDetails = lotteryTicket.playerDetails();
var playerAccount = playerDetails.bankAccount();
var result = LotteryUtils.checkTicketForPrize(repository, id, numbers).getResult();
if (result == LotteryTicketCheckResult.CheckResult.WIN_PRIZE) {
if (wireTransfers.transferFunds(PRIZE_AMOUNT, SERVICE_BANK_ACCOUNT, playerAccount)) {
notifications.ticketWon(playerDetails, PRIZE_AMOUNT);
} else {
notifications.prizeError(playerDetails, PRIZE_AMOUNT);
}
} else if (result == LotteryTicketCheckResult.CheckResult.NO_PRIZE) {
notifications.ticketDidNotWin(playerDetails);
}
}
return numbers;
}
/**
* Begin new lottery round.
*/
public void resetLottery() {
repository.deleteAll();
}
}
@@ -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.hexagonal.domain;
/**
* Lottery domain constants.
*/
public class LotteryConstants {
private LotteryConstants() {
}
public static final int PRIZE_AMOUNT = 100000;
public static final String SERVICE_BANK_ACCOUNT = "123-123";
public static final int TICKET_PRIZE = 3;
public static final int SERVICE_BANK_ACCOUNT_BALANCE = 150000;
public static final int PLAYER_MAX_BALANCE = 100;
}
@@ -0,0 +1,142 @@
/*
* 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.hexagonal.domain;
import com.google.common.base.Joiner;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashSet;
import java.util.PrimitiveIterator;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Value object representing lottery numbers. This lottery uses sets of 4 numbers. The numbers must
* be unique and between 1 and 20.
*/
@EqualsAndHashCode
@ToString
public class LotteryNumbers {
private final Set<Integer> numbers;
public static final int MIN_NUMBER = 1;
public static final int MAX_NUMBER = 20;
public static final int NUM_NUMBERS = 4;
/**
* Constructor. Creates random lottery numbers.
*/
private LotteryNumbers() {
numbers = new HashSet<>();
generateRandomNumbers();
}
/**
* Constructor. Uses given numbers.
*/
private LotteryNumbers(Set<Integer> givenNumbers) {
numbers = new HashSet<>();
numbers.addAll(givenNumbers);
}
/**
* Creates a random lottery number.
*
* @return random LotteryNumbers
*/
public static LotteryNumbers createRandom() {
return new LotteryNumbers();
}
/**
* Creates lottery number from given set of numbers.
*
* @return given LotteryNumbers
*/
public static LotteryNumbers create(Set<Integer> givenNumbers) {
return new LotteryNumbers(givenNumbers);
}
/**
* Get numbers.
*
* @return lottery numbers
*/
public Set<Integer> getNumbers() {
return Collections.unmodifiableSet(numbers);
}
/**
* Get numbers as string.
*
* @return numbers as comma separated string
*/
public String getNumbersAsString() {
return Joiner.on(',').join(numbers);
}
/**
* Generates 4 unique random numbers between 1-20 into numbers set.
*/
private void generateRandomNumbers() {
numbers.clear();
var generator = new RandomNumberGenerator(MIN_NUMBER, MAX_NUMBER);
while (numbers.size() < NUM_NUMBERS) {
var num = generator.nextInt();
numbers.add(num);
}
}
/**
* Helper class for generating random numbers.
*/
private static class RandomNumberGenerator {
private final PrimitiveIterator.OfInt randomIterator;
/**
* Initialize a new random number generator that generates random numbers in the range [min,
* max].
*
* @param min the min value (inclusive)
* @param max the max value (inclusive)
*/
public RandomNumberGenerator(int min, int max) {
randomIterator = new SecureRandom().ints(min, max + 1).iterator();
}
/**
* Gets next random integer in [min, max] range.
*
* @return a random number in the range (min, max)
*/
public int nextInt() {
return randomIterator.nextInt();
}
}
}
@@ -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.hexagonal.domain;
import static com.iluwatar.hexagonal.domain.LotteryConstants.SERVICE_BANK_ACCOUNT;
import static com.iluwatar.hexagonal.domain.LotteryConstants.TICKET_PRIZE;
import com.google.inject.Inject;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.database.LotteryTicketRepository;
import com.iluwatar.hexagonal.eventlog.LotteryEventLog;
import java.util.Optional;
/**
* Implementation for lottery service.
*/
public class LotteryService {
private final LotteryTicketRepository repository;
private final LotteryEventLog notifications;
private final WireTransfers wireTransfers;
/**
* Constructor.
*/
@Inject
public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications,
WireTransfers wireTransfers) {
this.repository = repository;
this.notifications = notifications;
this.wireTransfers = wireTransfers;
}
/**
* Submit lottery ticket to participate in the lottery.
*/
public Optional<LotteryTicketId> submitTicket(LotteryTicket ticket) {
var playerDetails = ticket.playerDetails();
var playerAccount = playerDetails.bankAccount();
var result = wireTransfers.transferFunds(TICKET_PRIZE, playerAccount, SERVICE_BANK_ACCOUNT);
if (!result) {
notifications.ticketSubmitError(playerDetails);
return Optional.empty();
}
var optional = repository.save(ticket);
if (optional.isPresent()) {
notifications.ticketSubmitted(playerDetails);
}
return optional;
}
/**
* Check if lottery ticket has won.
*/
public LotteryTicketCheckResult checkTicketForPrize(
LotteryTicketId id,
LotteryNumbers winningNumbers
) {
return LotteryUtils.checkTicketForPrize(repository, id, winningNumbers);
}
}
@@ -0,0 +1,66 @@
/*
* 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.hexagonal.domain;
/**
* Immutable value object representing lottery ticket.
*/
public record LotteryTicket(LotteryTicketId id, PlayerDetails playerDetails, LotteryNumbers lotteryNumbers) {
@Override
public int hashCode() {
final var prime = 31;
var result = 1;
result = prime * result + ((lotteryNumbers == null) ? 0 : lotteryNumbers.hashCode());
result = prime * result + ((playerDetails == null) ? 0 : playerDetails.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
var other = (LotteryTicket) obj;
if (lotteryNumbers == null) {
if (other.lotteryNumbers != null) {
return false;
}
} else if (!lotteryNumbers.equals(other.lotteryNumbers)) {
return false;
}
if (playerDetails == null) {
return other.playerDetails == null;
} else {
return playerDetails.equals(other.playerDetails);
}
}
}
@@ -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.hexagonal.domain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Represents lottery ticket check result.
*/
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor
public class LotteryTicketCheckResult {
/**
* Enumeration of Type of Outcomes of a Lottery.
*/
public enum CheckResult {
WIN_PRIZE,
NO_PRIZE,
TICKET_NOT_SUBMITTED
}
private final CheckResult result;
private final int prizeAmount;
/**
* Constructor.
*/
public LotteryTicketCheckResult(CheckResult result) {
this.result = result;
prizeAmount = 0;
}
}
@@ -0,0 +1,52 @@
/*
* 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.hexagonal.domain;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Lottery ticked id.
*/
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor
public class LotteryTicketId {
private static final AtomicInteger numAllocated = new AtomicInteger(0);
private final int id;
public LotteryTicketId() {
this.id = numAllocated.incrementAndGet();
}
@Override
public String toString() {
return String.format("%d", id);
}
}
@@ -0,0 +1,57 @@
/*
* 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.hexagonal.domain;
import com.iluwatar.hexagonal.database.LotteryTicketRepository;
import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult;
/**
* Lottery utilities.
*/
public class LotteryUtils {
private LotteryUtils() {
}
/**
* Checks if lottery ticket has won.
*/
public static LotteryTicketCheckResult checkTicketForPrize(
LotteryTicketRepository repository,
LotteryTicketId id,
LotteryNumbers winningNumbers
) {
var optional = repository.findById(id);
if (optional.isPresent()) {
if (optional.get().lotteryNumbers().equals(winningNumbers)) {
return new LotteryTicketCheckResult(CheckResult.WIN_PRIZE, 1000);
} else {
return new LotteryTicketCheckResult(CheckResult.NO_PRIZE);
}
} else {
return new LotteryTicketCheckResult(CheckResult.TICKET_NOT_SUBMITTED);
}
}
}
@@ -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.
*/
package com.iluwatar.hexagonal.domain;
/**
* Immutable value object containing lottery player details.
*/
public record PlayerDetails(String email, String bankAccount, String phoneNumber) {}
@@ -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.hexagonal.eventlog;
import com.iluwatar.hexagonal.domain.PlayerDetails;
/**
* Event log for lottery events.
*/
public interface LotteryEventLog {
/**
* lottery ticket submitted.
*/
void ticketSubmitted(PlayerDetails details);
/**
* error submitting lottery ticket.
*/
void ticketSubmitError(PlayerDetails details);
/**
* lottery ticket did not win.
*/
void ticketDidNotWin(PlayerDetails details);
/**
* lottery ticket won.
*/
void ticketWon(PlayerDetails details, int prizeAmount);
/**
* error paying the prize.
*/
void prizeError(PlayerDetails details, int prizeAmount);
}
@@ -0,0 +1,142 @@
/*
* 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.hexagonal.eventlog;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import lombok.Getter;
import org.bson.Document;
/**
* Mongo based event log.
*/
public class MongoEventLog implements LotteryEventLog {
private static final String DEFAULT_DB = "lotteryDB";
private static final String DEFAULT_EVENTS_COLLECTION = "events";
private static final String EMAIL = "email";
private static final String PHONE = "phone";
public static final String MESSAGE = "message";
@Getter
private MongoClient mongoClient;
@Getter
private MongoDatabase database;
@Getter
private MongoCollection<Document> eventsCollection;
private final StdOutEventLog stdOutEventLog = new StdOutEventLog();
/**
* Constructor.
*/
public MongoEventLog() {
connect();
}
/**
* Constructor accepting parameters.
*/
public MongoEventLog(String dbName, String eventsCollectionName) {
connect(dbName, eventsCollectionName);
}
/**
* Connect to database with default parameters.
*/
public void connect() {
connect(DEFAULT_DB, DEFAULT_EVENTS_COLLECTION);
}
/**
* Connect to database with given parameters.
*/
public void connect(String dbName, String eventsCollectionName) {
if (mongoClient != null) {
mongoClient.close();
}
mongoClient = new MongoClient(System.getProperty("mongo-host"),
Integer.parseInt(System.getProperty("mongo-port")));
database = mongoClient.getDatabase(dbName);
eventsCollection = database.getCollection(eventsCollectionName);
}
@Override
public void ticketSubmitted(PlayerDetails details) {
var document = new Document(EMAIL, details.email());
document.put(PHONE, details.phoneNumber());
document.put("bank", details.bankAccount());
document
.put(MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits.");
eventsCollection.insertOne(document);
stdOutEventLog.ticketSubmitted(details);
}
@Override
public void ticketSubmitError(PlayerDetails details) {
var document = new Document(EMAIL, details.email());
document.put(PHONE, details.phoneNumber());
document.put("bank", details.bankAccount());
document.put(MESSAGE, "Lottery ticket could not be submitted because lack of funds.");
eventsCollection.insertOne(document);
stdOutEventLog.ticketSubmitError(details);
}
@Override
public void ticketDidNotWin(PlayerDetails details) {
var document = new Document(EMAIL, details.email());
document.put(PHONE, details.phoneNumber());
document.put("bank", details.bankAccount());
document.put(MESSAGE, "Lottery ticket was checked and unfortunately did not win this time.");
eventsCollection.insertOne(document);
stdOutEventLog.ticketDidNotWin(details);
}
@Override
public void ticketWon(PlayerDetails details, int prizeAmount) {
var document = new Document(EMAIL, details.email());
document.put(PHONE, details.phoneNumber());
document.put("bank", details.bankAccount());
document.put(MESSAGE, String
.format("Lottery ticket won! The bank account was deposited with %d credits.",
prizeAmount));
eventsCollection.insertOne(document);
stdOutEventLog.ticketWon(details, prizeAmount);
}
@Override
public void prizeError(PlayerDetails details, int prizeAmount) {
var document = new Document(EMAIL, details.email());
document.put(PHONE, details.phoneNumber());
document.put("bank", details.bankAccount());
document.put(MESSAGE, String
.format("Lottery ticket won! Unfortunately the bank credit transfer of %d failed.",
prizeAmount));
eventsCollection.insertOne(document);
stdOutEventLog.prizeError(details, prizeAmount);
}
}
@@ -0,0 +1,65 @@
/*
* 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.hexagonal.eventlog;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import lombok.extern.slf4j.Slf4j;
/**
* Standard output event log.
*/
@Slf4j
public class StdOutEventLog implements LotteryEventLog {
@Override
public void ticketSubmitted(PlayerDetails details) {
LOGGER.info("Lottery ticket for {} was submitted. Bank account {} was charged for 3 credits.",
details.email(), details.bankAccount());
}
@Override
public void ticketDidNotWin(PlayerDetails details) {
LOGGER.info("Lottery ticket for {} was checked and unfortunately did not win this time.",
details.email());
}
@Override
public void ticketWon(PlayerDetails details, int prizeAmount) {
LOGGER.info("Lottery ticket for {} has won! The bank account {} was deposited with {} credits.",
details.email(), details.bankAccount(), prizeAmount);
}
@Override
public void prizeError(PlayerDetails details, int prizeAmount) {
LOGGER.error("Lottery ticket for {} has won! Unfortunately the bank credit transfer of"
+ " {} failed.", details.email(), prizeAmount);
}
@Override
public void ticketSubmitError(PlayerDetails details) {
LOGGER.error("Lottery ticket for {} could not be submitted because the credit transfer"
+ " of 3 credits failed.", details.email());
}
}
@@ -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.hexagonal.module;
import com.google.inject.AbstractModule;
import com.iluwatar.hexagonal.banking.MongoBank;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.database.LotteryTicketRepository;
import com.iluwatar.hexagonal.database.MongoTicketRepository;
import com.iluwatar.hexagonal.eventlog.LotteryEventLog;
import com.iluwatar.hexagonal.eventlog.MongoEventLog;
/**
* Guice module for binding production dependencies.
*/
public class LotteryModule extends AbstractModule {
@Override
protected void configure() {
bind(LotteryTicketRepository.class).to(MongoTicketRepository.class);
bind(LotteryEventLog.class).to(MongoEventLog.class);
bind(WireTransfers.class).to(MongoBank.class);
}
}
@@ -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.hexagonal.module;
import com.google.inject.AbstractModule;
import com.iluwatar.hexagonal.banking.InMemoryBank;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.database.InMemoryTicketRepository;
import com.iluwatar.hexagonal.database.LotteryTicketRepository;
import com.iluwatar.hexagonal.eventlog.LotteryEventLog;
import com.iluwatar.hexagonal.eventlog.StdOutEventLog;
/**
* Guice module for testing dependencies.
*/
public class LotteryTestingModule extends AbstractModule {
@Override
protected void configure() {
bind(LotteryTicketRepository.class).to(InMemoryTicketRepository.class);
bind(LotteryEventLog.class).to(StdOutEventLog.class);
bind(WireTransfers.class).to(InMemoryBank.class);
}
}
@@ -0,0 +1,61 @@
/*
* 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.hexagonal.mongo;
import java.io.FileInputStream;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
/**
* Mongo connection properties loader.
*/
@Slf4j
public class MongoConnectionPropertiesLoader {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 27017;
/**
* Try to load connection properties from file. Fall back to default connection properties.
*/
public static void load() {
var host = DEFAULT_HOST;
var port = DEFAULT_PORT;
var path = System.getProperty("hexagonal.properties.path");
var properties = new Properties();
if (path != null) {
try (var fin = new FileInputStream(path)) {
properties.load(fin);
host = properties.getProperty("mongo-host");
port = Integer.parseInt(properties.getProperty("mongo-port"));
} catch (Exception e) {
// error occurred, use default properties
LOGGER.error("Error occurred: ", e);
}
}
System.setProperty("mongo-host", host);
System.setProperty("mongo-port", String.format("%d", port));
}
}
@@ -0,0 +1,113 @@
/*
* 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.hexagonal.sampledata;
import com.iluwatar.hexagonal.banking.InMemoryBank;
import com.iluwatar.hexagonal.domain.LotteryConstants;
import com.iluwatar.hexagonal.domain.LotteryNumbers;
import com.iluwatar.hexagonal.domain.LotteryService;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import java.security.SecureRandom;
import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.stream.Collectors;
/**
* Utilities for creating sample lottery tickets.
*/
public class SampleData {
private static final List<PlayerDetails> PLAYERS;
private static final SecureRandom RANDOM = new SecureRandom();
static {
PLAYERS = List.of(
new PlayerDetails("john@google.com", "312-342", "+3242434242"),
new PlayerDetails("mary@google.com", "234-987", "+23452346"),
new PlayerDetails("steve@google.com", "833-836", "+63457543"),
new PlayerDetails("wayne@google.com", "319-826", "+24626"),
new PlayerDetails("johnie@google.com", "983-322", "+3635635"),
new PlayerDetails("andy@google.com", "934-734", "+0898245"),
new PlayerDetails("richard@google.com", "536-738", "+09845325"),
new PlayerDetails("kevin@google.com", "453-936", "+2423532"),
new PlayerDetails("arnold@google.com", "114-988", "+5646346524"),
new PlayerDetails("ian@google.com", "663-765", "+928394235"),
new PlayerDetails("robin@google.com", "334-763", "+35448"),
new PlayerDetails("ted@google.com", "735-964", "+98752345"),
new PlayerDetails("larry@google.com", "734-853", "+043842423"),
new PlayerDetails("calvin@google.com", "334-746", "+73294135"),
new PlayerDetails("jacob@google.com", "444-766", "+358042354"),
new PlayerDetails("edwin@google.com", "895-345", "+9752435"),
new PlayerDetails("mary@google.com", "760-009", "+34203542"),
new PlayerDetails("lolita@google.com", "425-907", "+9872342"),
new PlayerDetails("bruno@google.com", "023-638", "+673824122"),
new PlayerDetails("peter@google.com", "335-886", "+5432503945"),
new PlayerDetails("warren@google.com", "225-946", "+9872341324"),
new PlayerDetails("monica@google.com", "265-748", "+134124"),
new PlayerDetails("ollie@google.com", "190-045", "+34453452"),
new PlayerDetails("yngwie@google.com", "241-465", "+9897641231"),
new PlayerDetails("lars@google.com", "746-936", "+42345298345"),
new PlayerDetails("bobbie@google.com", "946-384", "+79831742"),
new PlayerDetails("tyron@google.com", "310-992", "+0498837412"),
new PlayerDetails("tyrell@google.com", "032-045", "+67834134"),
new PlayerDetails("nadja@google.com", "000-346", "+498723"),
new PlayerDetails("wendy@google.com", "994-989", "+987324454"),
new PlayerDetails("luke@google.com", "546-634", "+987642435"),
new PlayerDetails("bjorn@google.com", "342-874", "+7834325"),
new PlayerDetails("lisa@google.com", "024-653", "+980742154"),
new PlayerDetails("anton@google.com", "834-935", "+876423145"),
new PlayerDetails("bruce@google.com", "284-936", "+09843212345"),
new PlayerDetails("ray@google.com", "843-073", "+678324123"),
new PlayerDetails("ron@google.com", "637-738", "+09842354"),
new PlayerDetails("xavier@google.com", "143-947", "+375245"),
new PlayerDetails("harriet@google.com", "842-404", "+131243252")
);
var wireTransfers = new InMemoryBank();
PLAYERS.stream()
.map(PlayerDetails::bankAccount)
.map(e -> new SimpleEntry<>(e, RANDOM.nextInt(LotteryConstants.PLAYER_MAX_BALANCE)))
.collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue))
.forEach(wireTransfers::setFunds);
}
/**
* Inserts lottery tickets into the database based on the sample data.
*/
public static void submitTickets(LotteryService lotteryService, int numTickets) {
for (var i = 0; i < numTickets; i++) {
var randomPlayerDetails = getRandomPlayerDetails();
var lotteryNumbers = LotteryNumbers.createRandom();
var lotteryTicketId = new LotteryTicketId();
var ticket = new LotteryTicket(lotteryTicketId, randomPlayerDetails, lotteryNumbers);
lotteryService.submitTicket(ticket);
}
}
private static PlayerDetails getRandomPlayerDetails() {
return PLAYERS.get(RANDOM.nextInt(PLAYERS.size()));
}
}
@@ -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.hexagonal.service;
import com.google.inject.Guice;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.domain.LotteryService;
import com.iluwatar.hexagonal.module.LotteryModule;
import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader;
import java.util.Scanner;
import lombok.extern.slf4j.Slf4j;
/**
* Console interface for lottery players.
*/
@Slf4j
public class ConsoleLottery {
/**
* Program entry point.
*/
public static void main(String[] args) {
MongoConnectionPropertiesLoader.load();
var injector = Guice.createInjector(new LotteryModule());
var service = injector.getInstance(LotteryService.class);
var bank = injector.getInstance(WireTransfers.class);
try (Scanner scanner = new Scanner(System.in)) {
var exit = false;
while (!exit) {
printMainMenu();
var cmd = readString(scanner);
var lotteryConsoleService = new LotteryConsoleServiceImpl(LOGGER);
if ("1".equals(cmd)) {
lotteryConsoleService.queryLotteryAccountFunds(bank, scanner);
} else if ("2".equals(cmd)) {
lotteryConsoleService.addFundsToLotteryAccount(bank, scanner);
} else if ("3".equals(cmd)) {
lotteryConsoleService.submitTicket(service, scanner);
} else if ("4".equals(cmd)) {
lotteryConsoleService.checkTicket(service, scanner);
} else if ("5".equals(cmd)) {
exit = true;
} else {
LOGGER.info("Unknown command");
}
}
}
}
private static void printMainMenu() {
LOGGER.info("");
LOGGER.info("### Lottery Service Console ###");
LOGGER.info("(1) Query lottery account funds");
LOGGER.info("(2) Add funds to lottery account");
LOGGER.info("(3) Submit ticket");
LOGGER.info("(4) Check ticket");
LOGGER.info("(5) Exit");
}
private static String readString(Scanner scanner) {
LOGGER.info("> ");
return scanner.next();
}
}
@@ -0,0 +1,55 @@
/*
* 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.hexagonal.service;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.domain.LotteryService;
import java.util.Scanner;
/**
* Console interface for lottery service.
*/
public interface LotteryConsoleService {
void checkTicket(LotteryService service, Scanner scanner);
/**
* Submit lottery ticket to participate in the lottery.
*/
void submitTicket(LotteryService service, Scanner scanner);
/**
* Add funds to lottery account.
*/
void addFundsToLotteryAccount(WireTransfers bank, Scanner scanner);
/**
* Recovery funds from lottery account.
*/
void queryLotteryAccountFunds(WireTransfers bank, Scanner scanner);
}
@@ -0,0 +1,128 @@
/*
* 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.hexagonal.service;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.domain.LotteryNumbers;
import com.iluwatar.hexagonal.domain.LotteryService;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import java.util.Arrays;
import java.util.Scanner;
import java.util.stream.Collectors;
import org.slf4j.Logger;
/**
* Console implementation for lottery console service.
*/
public class LotteryConsoleServiceImpl implements LotteryConsoleService {
private final Logger logger;
/**
* Constructor.
*/
public LotteryConsoleServiceImpl(Logger logger) {
this.logger = logger;
}
@Override
public void checkTicket(LotteryService service, Scanner scanner) {
logger.info("What is the ID of the lottery ticket?");
var id = readString(scanner);
logger.info("Give the 4 comma separated winning numbers?");
var numbers = readString(scanner);
try {
var winningNumbers = Arrays.stream(numbers.split(","))
.map(Integer::parseInt)
.limit(4)
.collect(Collectors.toSet());
final var lotteryTicketId = new LotteryTicketId(Integer.parseInt(id));
final var lotteryNumbers = LotteryNumbers.create(winningNumbers);
var result = service.checkTicketForPrize(lotteryTicketId, lotteryNumbers);
if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.WIN_PRIZE)) {
logger.info("Congratulations! The lottery ticket has won!");
} else if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.NO_PRIZE)) {
logger.info("Unfortunately the lottery ticket did not win.");
} else {
logger.info("Such lottery ticket has not been submitted.");
}
} catch (Exception e) {
logger.info("Failed checking the lottery ticket - please try again.");
}
}
@Override
public void submitTicket(LotteryService service, Scanner scanner) {
logger.info("What is your email address?");
var email = readString(scanner);
logger.info("What is your bank account number?");
var account = readString(scanner);
logger.info("What is your phone number?");
var phone = readString(scanner);
var details = new PlayerDetails(email, account, phone);
logger.info("Give 4 comma separated lottery numbers?");
var numbers = readString(scanner);
try {
var chosen = Arrays.stream(numbers.split(","))
.map(Integer::parseInt)
.collect(Collectors.toSet());
var lotteryNumbers = LotteryNumbers.create(chosen);
var lotteryTicket = new LotteryTicket(new LotteryTicketId(), details, lotteryNumbers);
service.submitTicket(lotteryTicket).ifPresentOrElse(
(id) -> logger.info("Submitted lottery ticket with id: {}", id),
() -> logger.info("Failed submitting lottery ticket - please try again.")
);
} catch (Exception e) {
logger.info("Failed submitting lottery ticket - please try again.");
}
}
@Override
public void addFundsToLotteryAccount(WireTransfers bank, Scanner scanner) {
logger.info("What is the account number?");
var account = readString(scanner);
logger.info("How many credits do you want to deposit?");
var amount = readString(scanner);
bank.setFunds(account, Integer.parseInt(amount));
logger.info("The account {} now has {} credits.", account, bank.getFunds(account));
}
@Override
public void queryLotteryAccountFunds(WireTransfers bank, Scanner scanner) {
logger.info("What is the account number?");
var account = readString(scanner);
logger.info("The account {} has {} credits.", account, bank.getFunds(account));
}
private String readString(Scanner scanner) {
logger.info("> ");
return scanner.next();
}
}
@@ -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.hexagonal;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Unit test for simple App.
*/
class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}
@@ -0,0 +1,50 @@
/*
* 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.hexagonal.banking;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
/**
* Tests for banking
*/
class InMemoryBankTest {
private final WireTransfers bank = new InMemoryBank();
@Test
void testInit() {
assertEquals(0, bank.getFunds("foo"));
bank.setFunds("foo", 100);
assertEquals(100, bank.getFunds("foo"));
bank.setFunds("bar", 150);
assertEquals(150, bank.getFunds("bar"));
assertTrue(bank.transferFunds(50, "bar", "foo"));
assertEquals(150, bank.getFunds("foo"));
assertEquals(100, bank.getFunds("bar"));
}
}
@@ -0,0 +1,100 @@
/*
* 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.hexagonal.banking;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import de.flapdoodle.embed.mongo.commands.ServerAddress;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.mongo.transitions.Mongod;
import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess;
import de.flapdoodle.reverse.TransitionWalker;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Tests for Mongo banking adapter
*/
class MongoBankTest {
private static final String TEST_DB = "lotteryDBTest";
private static final String TEST_ACCOUNTS_COLLECTION = "testAccounts";
private static MongoClient mongoClient;
private static MongoDatabase mongoDatabase;
private MongoBank mongoBank;
private static TransitionWalker.ReachedState<RunningMongodProcess> mongodProcess;
private static ServerAddress serverAddress;
@BeforeAll
static void setUp() {
mongodProcess = Mongod.instance().start(Version.Main.V7_0);
serverAddress = mongodProcess.current().getServerAddress();
mongoClient = MongoClients.create("mongodb://" + serverAddress.toString());
mongoClient.startSession();
mongoDatabase = mongoClient.getDatabase(TEST_DB);
}
@AfterAll
static void tearDown() {
mongoClient.close();
mongodProcess.close();
}
@BeforeEach
void init() {
System.setProperty("mongo-host", serverAddress.getHost());
System.setProperty("mongo-port", String.valueOf(serverAddress.getPort()));
mongoDatabase.drop();
mongoBank = new MongoBank(mongoDatabase.getName(), TEST_ACCOUNTS_COLLECTION);
}
@Test
void testSetup() {
assertEquals(0, mongoBank.getAccountsCollection().countDocuments());
}
@Test
void testFundTransfers() {
assertEquals(0, mongoBank.getFunds("000-000"));
mongoBank.setFunds("000-000", 10);
assertEquals(10, mongoBank.getFunds("000-000"));
assertEquals(0, mongoBank.getFunds("111-111"));
mongoBank.transferFunds(9, "000-000", "111-111");
assertEquals(1, mongoBank.getFunds("000-000"));
assertEquals(9, mongoBank.getFunds("111-111"));
}
}
@@ -0,0 +1,57 @@
/*
* 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.hexagonal.database;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.hexagonal.test.LotteryTestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link LotteryTicketRepository}
*/
class InMemoryTicketRepositoryTest {
private final LotteryTicketRepository repository = new InMemoryTicketRepository();
@BeforeEach
void clear() {
repository.deleteAll();
}
@Test
void testCrudOperations() {
var repository = new InMemoryTicketRepository();
assertTrue(repository.findAll().isEmpty());
var ticket = LotteryTestUtils.createLotteryTicket();
var id = repository.save(ticket);
assertTrue(id.isPresent());
assertEquals(1, repository.findAll().size());
var optionalTicket = repository.findById(id.get());
assertTrue(optionalTicket.isPresent());
}
}
@@ -0,0 +1,97 @@
/*
* 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.hexagonal.database;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.hexagonal.domain.LotteryNumbers;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader;
import com.mongodb.MongoClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* Tests for Mongo based ticket repository
*/
@Disabled
class MongoTicketRepositoryTest {
private static final String TEST_DB = "lotteryTestDB";
private static final String TEST_TICKETS_COLLECTION = "lotteryTestTickets";
private static final String TEST_COUNTERS_COLLECTION = "testCounters";
private MongoTicketRepository repository;
@BeforeEach
void init() {
MongoConnectionPropertiesLoader.load();
var mongoClient = new MongoClient(System.getProperty("mongo-host"),
Integer.parseInt(System.getProperty("mongo-port")));
mongoClient.dropDatabase(TEST_DB);
mongoClient.close();
repository = new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION,
TEST_COUNTERS_COLLECTION);
}
@Test
void testSetup() {
assertEquals(1, repository.getCountersCollection().countDocuments());
assertEquals(0, repository.getTicketsCollection().countDocuments());
}
@Test
void testNextId() {
assertEquals(1, repository.getNextId());
assertEquals(2, repository.getNextId());
assertEquals(3, repository.getNextId());
}
@Test
void testCrudOperations() {
// create new lottery ticket and save it
var details = new PlayerDetails("foo@bar.com", "123-123", "07001234");
var random = LotteryNumbers.createRandom();
var original = new LotteryTicket(new LotteryTicketId(), details, random);
var saved = repository.save(original);
assertEquals(1, repository.getTicketsCollection().countDocuments());
assertTrue(saved.isPresent());
// fetch the saved lottery ticket from database and check its contents
var found = repository.findById(saved.get());
assertTrue(found.isPresent());
var ticket = found.get();
assertEquals("foo@bar.com", ticket.playerDetails().email());
assertEquals("123-123", ticket.playerDetails().bankAccount());
assertEquals("07001234", ticket.playerDetails().phoneNumber());
assertEquals(original.lotteryNumbers(), ticket.lotteryNumbers());
// clear the collection
repository.deleteAll();
assertEquals(0, repository.getTicketsCollection().countDocuments());
}
}
@@ -0,0 +1,70 @@
/*
* 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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Set;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link LotteryNumbers}
*/
class LotteryNumbersTest {
@Test
void testGivenNumbers() {
var numbers = LotteryNumbers.create(Set.of(1, 2, 3, 4));
assertEquals(numbers.getNumbers().size(), 4);
assertTrue(numbers.getNumbers().contains(1));
assertTrue(numbers.getNumbers().contains(2));
assertTrue(numbers.getNumbers().contains(3));
assertTrue(numbers.getNumbers().contains(4));
}
@Test
void testNumbersCantBeModified() {
var numbers = LotteryNumbers.create(Set.of(1, 2, 3, 4));
assertThrows(UnsupportedOperationException.class, () -> numbers.getNumbers().add(5));
}
@Test
void testRandomNumbers() {
var numbers = LotteryNumbers.createRandom();
assertEquals(numbers.getNumbers().size(), LotteryNumbers.NUM_NUMBERS);
}
@Test
void testEquals() {
var numbers1 = LotteryNumbers.create(Set.of(1, 2, 3, 4));
var numbers2 = LotteryNumbers.create(Set.of(1, 2, 3, 4));
assertEquals(numbers1, numbers2);
var numbers3 = LotteryNumbers.create(Set.of(11, 12, 13, 14));
assertNotEquals(numbers1, numbers3);
}
}
@@ -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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.iluwatar.hexagonal.banking.WireTransfers;
import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult;
import com.iluwatar.hexagonal.module.LotteryTestingModule;
import com.iluwatar.hexagonal.test.LotteryTestUtils;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test the lottery system
*/
class LotteryTest {
private final Injector injector;
@Inject
private LotteryAdministration administration;
@Inject
private LotteryService service;
@Inject
private WireTransfers wireTransfers;
LotteryTest() {
this.injector = Guice.createInjector(new LotteryTestingModule());
}
@BeforeEach
void setup() {
injector.injectMembers(this);
// add funds to the test player's bank account
wireTransfers.setFunds("123-12312", 100);
}
@Test
void testLottery() {
// admin resets the lottery
administration.resetLottery();
assertEquals(0, administration.getAllSubmittedTickets().size());
// players submit the lottery tickets
var ticket1 = service.submitTicket(LotteryTestUtils.createLotteryTicket("cvt@bbb.com",
"123-12312", "+32425255", Set.of(1, 2, 3, 4)));
assertTrue(ticket1.isPresent());
var ticket2 = service.submitTicket(LotteryTestUtils.createLotteryTicket("ant@bac.com",
"123-12312", "+32423455", Set.of(11, 12, 13, 14)));
assertTrue(ticket2.isPresent());
var ticket3 = service.submitTicket(LotteryTestUtils.createLotteryTicket("arg@boo.com",
"123-12312", "+32421255", Set.of(6, 8, 13, 19)));
assertTrue(ticket3.isPresent());
assertEquals(3, administration.getAllSubmittedTickets().size());
// perform lottery
var winningNumbers = administration.performLottery();
// cheat a bit for testing sake, use winning numbers to submit another ticket
var ticket4 = service.submitTicket(LotteryTestUtils.createLotteryTicket("lucky@orb.com",
"123-12312", "+12421255", winningNumbers.getNumbers()));
assertTrue(ticket4.isPresent());
assertEquals(4, administration.getAllSubmittedTickets().size());
// check winners
var tickets = administration.getAllSubmittedTickets();
for (var id : tickets.keySet()) {
var checkResult = service.checkTicketForPrize(id, winningNumbers);
assertNotEquals(CheckResult.TICKET_NOT_SUBMITTED, checkResult.getResult());
if (checkResult.getResult().equals(CheckResult.WIN_PRIZE)) {
assertTrue(checkResult.getPrizeAmount() > 0);
} else {
assertEquals(0, checkResult.getPrizeAmount());
}
}
// check another ticket that has not been submitted
var checkResult = service.checkTicketForPrize(new LotteryTicketId(), winningNumbers);
assertEquals(CheckResult.TICKET_NOT_SUBMITTED, checkResult.getResult());
assertEquals(0, checkResult.getPrizeAmount());
}
}
@@ -0,0 +1,46 @@
/*
* 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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link LotteryTicketCheckResult}
*/
class LotteryTicketCheckResultTest {
@Test
void testEquals() {
var result1 = new LotteryTicketCheckResult(CheckResult.NO_PRIZE);
var result2 = new LotteryTicketCheckResult(CheckResult.NO_PRIZE);
assertEquals(result1, result2);
var result3 = new LotteryTicketCheckResult(CheckResult.WIN_PRIZE, 300000);
assertNotEquals(result1, result3);
}
}
@@ -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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
/**
* Tests for lottery ticket id
*/
class LotteryTicketIdTest {
@Test
void testEquals() {
var ticketId1 = new LotteryTicketId();
var ticketId2 = new LotteryTicketId();
var ticketId3 = new LotteryTicketId();
assertNotEquals(ticketId1, ticketId2);
assertNotEquals(ticketId2, ticketId3);
var ticketId4 = new LotteryTicketId(ticketId1.getId());
assertEquals(ticketId1, ticketId4);
}
}
@@ -0,0 +1,52 @@
/*
* 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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.util.Set;
import org.junit.jupiter.api.Test;
/**
* Test Lottery Tickets for equality
*/
class LotteryTicketTest {
@Test
void testEquals() {
var details1 = new PlayerDetails("bob@foo.bar", "1212-121212", "+34332322");
var numbers1 = LotteryNumbers.create(Set.of(1, 2, 3, 4));
var ticket1 = new LotteryTicket(new LotteryTicketId(), details1, numbers1);
var details2 = new PlayerDetails("bob@foo.bar", "1212-121212", "+34332322");
var numbers2 = LotteryNumbers.create(Set.of(1, 2, 3, 4));
var ticket2 = new LotteryTicket(new LotteryTicketId(), details2, numbers2);
assertEquals(ticket1, ticket2);
var details3 = new PlayerDetails("elsa@foo.bar", "1223-121212", "+49332322");
var numbers3 = LotteryNumbers.create(Set.of(1, 2, 3, 8));
var ticket3 = new LotteryTicket(new LotteryTicketId(), details3, numbers3);
assertNotEquals(ticket1, ticket3);
}
}
@@ -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.hexagonal.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link PlayerDetails}
*/
class PlayerDetailsTest {
@Test
void testEquals() {
var details1 = new PlayerDetails("tom@foo.bar", "11212-123434", "+12323425");
var details2 = new PlayerDetails("tom@foo.bar", "11212-123434", "+12323425");
assertEquals(details1, details2);
var details3 = new PlayerDetails("john@foo.bar", "16412-123439", "+34323432");
assertNotEquals(details1, details3);
}
}
@@ -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.hexagonal.eventlog;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader;
import com.mongodb.MongoClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* Tests for Mongo event log
*/
@Disabled
class MongoEventLogTest {
private static final String TEST_DB = "lotteryDBTest";
private static final String TEST_EVENTS_COLLECTION = "testEvents";
private MongoEventLog mongoEventLog;
@BeforeEach
void init() {
MongoConnectionPropertiesLoader.load();
var mongoClient = new MongoClient(System.getProperty("mongo-host"),
Integer.parseInt(System.getProperty("mongo-port")));
mongoClient.dropDatabase(TEST_DB);
mongoClient.close();
mongoEventLog = new MongoEventLog(TEST_DB, TEST_EVENTS_COLLECTION);
}
@Test
void testSetup() {
assertEquals(0, mongoEventLog.getEventsCollection().countDocuments());
}
@Test
void testFundTransfers() {
var playerDetails = new PlayerDetails("john@wayne.com", "000-000", "03432534543");
mongoEventLog.prizeError(playerDetails, 1000);
assertEquals(1, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.prizeError(playerDetails, 1000);
assertEquals(2, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketDidNotWin(playerDetails);
assertEquals(3, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketDidNotWin(playerDetails);
assertEquals(4, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketSubmitError(playerDetails);
assertEquals(5, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketSubmitError(playerDetails);
assertEquals(6, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketSubmitted(playerDetails);
assertEquals(7, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketSubmitted(playerDetails);
assertEquals(8, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketWon(playerDetails, 1000);
assertEquals(9, mongoEventLog.getEventsCollection().countDocuments());
mongoEventLog.ticketWon(playerDetails, 1000);
assertEquals(10, mongoEventLog.getEventsCollection().countDocuments());
}
}
@@ -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.hexagonal.test;
import com.iluwatar.hexagonal.domain.LotteryNumbers;
import com.iluwatar.hexagonal.domain.LotteryTicket;
import com.iluwatar.hexagonal.domain.LotteryTicketId;
import com.iluwatar.hexagonal.domain.PlayerDetails;
import java.util.Set;
/**
* Utilities for lottery tests
*/
public class LotteryTestUtils {
/**
* @return lottery ticket
*/
public static LotteryTicket createLotteryTicket() {
return createLotteryTicket("foo@bar.com", "12231-213132", "+99324554", Set.of(1, 2, 3, 4));
}
/**
* @return lottery ticket
*/
public static LotteryTicket createLotteryTicket(String email, String account, String phone,
Set<Integer> givenNumbers) {
var details = new PlayerDetails(email, account, phone);
var numbers = LotteryNumbers.create(givenNumbers);
return new LotteryTicket(new LotteryTicketId(), details, numbers);
}
}