├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── 1 Hands-on Legacy Code ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── domainstory │ ├── Bank-Prozess2024-03-20_2025-05-08-engl.egn │ └── Bank-Prozess2025-05-08.egn ├── legacy-code.sonargraph │ ├── Analyzers │ │ └── ArchitectureCheck.xml │ ├── Architecture │ │ ├── BoundedContexts.arc │ │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ │ └── Developers.properties │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── application │ │ ├── AccountManagementService.java │ │ └── CreditService.java │ │ └── models │ │ ├── Account.java │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ └── Customer.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── application │ ├── AccountManagementServiceTest.java │ └── CreditServiceTest.java │ └── models │ ├── AccountTest.java │ ├── CreditTest.java │ └── CustomerTest.java ├── 2 Hands-on Bounded Contexts without Mapping ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── .settings │ ├── org.eclipse.core.resources.prefs │ ├── org.eclipse.jdt.apt.core.prefs │ ├── org.eclipse.jdt.core.prefs │ └── org.eclipse.m2e.core.prefs ├── bounded-contexts-without-mapping.sonargraph │ ├── Analyzers │ │ └── ArchitectureCheck.xml │ ├── Architecture │ │ ├── BoundedContexts.arc │ │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ │ └── Developers.properties │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ └── Customer.java │ │ └── credit │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditCustomer.java │ │ └── CreditService.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ └── credit │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java ├── 2 Hands-on Bounded Contexts ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── .settings │ └── org.eclipse.jdt.core.prefs ├── bounded-contexts.sonargraph │ ├── Analyzers │ │ └── ArchitectureCheck.xml │ ├── Architecture │ │ ├── BoundedContexts.arc │ │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ │ └── Developers.properties │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ └── Customer.java │ │ └── credit │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditCustomer.java │ │ └── CreditService.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ └── credit │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java ├── 3 Hands-on Value Objects without sharedKernel ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │ ├── main │ │ └── java │ │ │ └── de │ │ │ └── wps │ │ │ └── ddd │ │ │ └── banking │ │ │ ├── accounting │ │ │ ├── Account.java │ │ │ ├── AccountManagementService.java │ │ │ ├── AccountNumber.java │ │ │ ├── AccountNumberFactory.java │ │ │ ├── Amount.java │ │ │ ├── Customer.java │ │ │ ├── CustomerNumber.java │ │ │ └── CustomerNumberFactory.java │ │ │ └── credit │ │ │ ├── AccountNumber.java │ │ │ ├── Amount.java │ │ │ ├── Credit.java │ │ │ ├── CreditAccount.java │ │ │ ├── CreditCustomer.java │ │ │ ├── CreditNumber.java │ │ │ ├── CreditNumberFactory.java │ │ │ ├── CreditService.java │ │ │ └── CustomerNumber.java │ └── test │ │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── AccountManagementServiceTest.java │ │ ├── AccountTest.java │ │ ├── AmountTest.java │ │ └── CustomerTest.java │ │ └── credit │ │ ├── AmountTest.java │ │ ├── CreditCustomerTest.java │ │ ├── CreditServiceTest.java │ │ └── CreditTest.java └── value-objects-without-shared-kernel.sonargraph │ ├── Analyzers │ └── ArchitectureCheck.xml │ ├── Architecture │ ├── BoundedContexts.arc │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ └── Developers.properties │ └── system.sonargraph ├── 3 Hands-on Value Objects ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │ ├── main │ │ └── java │ │ │ └── de │ │ │ └── wps │ │ │ └── ddd │ │ │ └── banking │ │ │ ├── accounting │ │ │ ├── Account.java │ │ │ ├── AccountManagementService.java │ │ │ └── Customer.java │ │ │ ├── credit │ │ │ ├── Credit.java │ │ │ ├── CreditAccount.java │ │ │ ├── CreditCustomer.java │ │ │ └── CreditService.java │ │ │ └── sharedKernel │ │ │ ├── AccountNumber.java │ │ │ ├── AccountNumberFactory.java │ │ │ ├── Amount.java │ │ │ ├── CreditNumber.java │ │ │ ├── CreditNumberFactory.java │ │ │ ├── CustomerNumber.java │ │ │ └── CustomerNumberFactory.java │ └── test │ │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── ValueObjectArchTest.java │ │ ├── accounting │ │ ├── AccountManagementServiceTest.java │ │ ├── AccountTest.java │ │ └── CustomerTest.java │ │ ├── credit │ │ ├── CreditCustomerTest.java │ │ ├── CreditServiceTest.java │ │ └── CreditTest.java │ │ └── sharedKernel │ │ ├── AmountTest.java │ │ ├── CreditNumberFactoryTest.java │ │ └── CreditNumberTest.java └── value-objects.sonargraph │ ├── Analyzers │ └── ArchitectureCheck.xml │ ├── Architecture │ ├── BoundedContexts.arc │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ └── Developers.properties │ └── system.sonargraph ├── 4 Hands-on Rich domain model ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml ├── rich-domain-model.sonargraph │ ├── Analyzers │ │ └── ArchitectureCheck.xml │ ├── Architecture │ │ ├── BoundedContexts.arc │ │ ├── BoundedContextsCheatSheet.arc │ │ ├── Pattern.arc │ │ └── PatternCheatSheet.arc │ ├── Settings │ │ └── Developers.properties │ └── system.sonargraph └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ └── Customer.java │ │ ├── credit │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditCustomer.java │ │ └── CreditService.java │ │ └── sharedKernel │ │ ├── AccountNumber.java │ │ ├── AccountNumberFactory.java │ │ ├── Amount.java │ │ ├── CreditNumber.java │ │ ├── CreditNumberFactory.java │ │ ├── CustomerNumber.java │ │ └── CustomerNumberFactory.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── RichDomainModelArchTest.java │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ ├── credit │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java │ └── sharedKernel │ └── AmountTest.java ├── 5 Hands-on Cycle-free without sharedKernel ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── cycle-free-without-shared-kernel.sonargraph │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ ├── AccountNumber.java │ │ ├── AccountNumberFactory.java │ │ ├── Amount.java │ │ ├── Customer.java │ │ ├── CustomerNumber.java │ │ └── CustomerNumberFactory.java │ │ └── credit │ │ ├── Amount.java │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditAccountNumber.java │ │ ├── CreditCustomer.java │ │ ├── CreditNumber.java │ │ ├── CreditNumberFactory.java │ │ ├── CreditService.java │ │ └── CustomerNumber.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── CycleFreeWithoutSharedKernelArchTest.java │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ └── credit │ ├── AmountTest.java │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java ├── 5 Hands-on Cycle-free ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── cycle-free.sonargraph │ ├── Analyzers │ │ └── ArchitectureCheck.xml │ ├── Architecture │ │ ├── BoundedContexts.arc │ │ └── BoundedContextsCheatSheet.arc │ ├── Settings │ │ └── Developers.properties │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ └── Customer.java │ │ ├── credit │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditCustomer.java │ │ └── CreditService.java │ │ └── sharedKernel │ │ ├── AccountNumber.java │ │ ├── AccountNumberFactory.java │ │ ├── Amount.java │ │ ├── CreditNumber.java │ │ ├── CreditNumberFactory.java │ │ ├── CustomerNumber.java │ │ └── CustomerNumberFactory.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ ├── credit │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java │ └── sharedKernel │ └── AmountTest.java ├── 6 Hands-on Event Based ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── event-based.sonargraph │ └── system.sonargraph ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── wps │ │ └── ddd │ │ └── banking │ │ ├── accounting │ │ ├── Account.java │ │ ├── AccountManagementService.java │ │ ├── AccountNumber.java │ │ ├── AccountNumberFactory.java │ │ ├── Amount.java │ │ ├── Customer.java │ │ ├── CustomerNumber.java │ │ ├── CustomerNumberFactory.java │ │ └── CustomerRegistrationEventPublisher.java │ │ ├── accountingevents │ │ └── NewCustomerRegisteredEvent.java │ │ └── credit │ │ ├── Amount.java │ │ ├── Credit.java │ │ ├── CreditAccount.java │ │ ├── CreditAccountNumber.java │ │ ├── CreditCustomer.java │ │ ├── CreditNumber.java │ │ ├── CreditNumberFactory.java │ │ ├── CreditService.java │ │ ├── CustomerNumber.java │ │ └── CustomerRegistrationEventHandler.java │ └── test │ └── java │ └── de │ └── wps │ └── ddd │ └── banking │ ├── CycleFreeWithoutSharedKernelArchTest.java │ ├── accounting │ ├── AccountManagementServiceTest.java │ ├── AccountTest.java │ └── CustomerTest.java │ └── credit │ ├── AmountTest.java │ ├── CreditCustomerTest.java │ ├── CreditServiceTest.java │ └── CreditTest.java ├── LICENSE ├── README.md └── maven-parent └── pom.xml /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/java 3 | { 4 | "name": "Java", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/java:1-21-bullseye", 7 | 8 | "features": { 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "none", 11 | "installMaven": "true", 12 | "installGradle": "false" 13 | } 14 | } 15 | 16 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 17 | // "forwardPorts": [], 18 | 19 | // Use 'postCreateCommand' to run commands after the container is created. 20 | // "postCreateCommand": "java -version", 21 | 22 | // Configure tool-specific properties. 23 | // "customizations": {}, 24 | 25 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 26 | // "remoteUser": "root" 27 | } 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: DDD banking example Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - name: Checkout source code 11 | uses: actions/checkout@v4 12 | - name: Set up JDK 13 | uses: actions/setup-java@v4 14 | with: 15 | distribution: 'temurin' 16 | java-version: '21' 17 | cache: maven 18 | - name: Build 1 with Maven 19 | working-directory: "./1 Hands-on Legacy Code" 20 | run: ./mvnw --batch-mode --update-snapshots verify 21 | - name: Build 2a with Maven 22 | working-directory: "./2 Hands-on Bounded Contexts" 23 | run: ./mvnw --batch-mode --update-snapshots verify 24 | - name: Build 2b with Maven 25 | working-directory: "./2 Hands-on Bounded Contexts without Mapping" 26 | run: ./mvnw --batch-mode --update-snapshots verify 27 | - name: Build 3a with Maven 28 | working-directory: "./3 Hands-on Value Objects" 29 | run: ./mvnw --batch-mode --update-snapshots verify 30 | - name: Build 3b with Maven 31 | working-directory: "./3 Hands-on Value Objects without sharedKernel" 32 | run: ./mvnw --batch-mode --update-snapshots verify 33 | - name: Build 4 with Maven 34 | working-directory: "./4 Hands-on Rich domain model" 35 | run: ./mvnw --batch-mode --update-snapshots verify 36 | - name: Build 5a with Maven 37 | working-directory: "./5 Hands-on Cycle-free" 38 | run: ./mvnw --batch-mode --update-snapshots verify 39 | - name: Build 5b with Maven 40 | working-directory: "./5 Hands-on Cycle-free without sharedKernel" 41 | run: ./mvnw --batch-mode --update-snapshots verify 42 | - name: Build 6 with Maven 43 | working-directory: "./6 Hands-on Event Based" 44 | run: ./mvnw --batch-mode --update-snapshots verify 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven 2 | target 3 | 4 | # IntelliJ 5 | .idea 6 | *.iml 7 | 8 | # Compiled class file 9 | *.class 10 | 11 | # Log file 12 | *.log 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | .vscode/settings.json 32 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/.gitignore: -------------------------------------------------------------------------------- 1 | /.classpath 2 | /.project 3 | /bin/ 4 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/legacy-code.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/legacy-code.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnLegacyCode 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/legacy-code.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnLegacyCode 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/application/AccountManagementService" 9 | include "**/models/Account" 10 | include "**/models/Customer" 11 | 12 | // Allow dependencies from Accounting to Credit 13 | connect to Credit 14 | } 15 | 16 | artifact Credit 17 | { 18 | include "**/application/CreditService" 19 | include "**/models/Credit" 20 | include "**/models/CreditAccount" 21 | 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/legacy-code.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Thu Mar 07 09:47:48 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville 4 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/legacy-code.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | de.wps.ddd.banking 9 | maven-parent 10 | 1.0-SNAPSHOT 11 | ../maven-parent 12 | 13 | 14 | legacy-code 15 | 1 Hands-on Legacy Code 16 | 17 | 18 | 19 | org.junit.jupiter 20 | junit-jupiter-api 21 | 22 | 23 | org.junit.jupiter 24 | junit-jupiter-params 25 | 26 | 27 | org.assertj 28 | assertj-core 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | com.hello2morrow 40 | sonargraph-maven-plugin 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/main/java/de/wps/ddd/banking/models/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | public class Account { 4 | private float balance; 5 | private int accountNumber; 6 | private Customer accountOwner; 7 | 8 | public Account(int accountNumber, Customer accountOwner) { 9 | super(); 10 | this.balance = 0; 11 | this.accountNumber = accountNumber; 12 | this.accountOwner = accountOwner; 13 | } 14 | 15 | public float getBalance() { 16 | return balance; 17 | } 18 | 19 | public void setBalance(float balance) { 20 | this.balance = balance; 21 | } 22 | 23 | public int getAccountnumber() { 24 | return accountNumber; 25 | } 26 | 27 | public Customer getAccountowner() { 28 | return accountOwner; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/main/java/de/wps/ddd/banking/models/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | public class Credit { 4 | private float amountOfCredit; 5 | private int creditNumber; 6 | private Status status; 7 | private Customer customer; 8 | private CreditAccount account; 9 | 10 | public enum Status { 11 | applied, refused, granted, delayed, payed 12 | } 13 | 14 | public Credit(int creditNumber, Customer customer, float amountOfCredit) { 15 | super(); 16 | this.amountOfCredit = amountOfCredit; 17 | this.creditNumber = creditNumber; 18 | this.customer = customer; 19 | this.customer.getCreditList().add(this); 20 | this.status = Status.applied; 21 | } 22 | 23 | public float getAmountOfCredit() { 24 | return amountOfCredit; 25 | } 26 | 27 | public void setAmountOfCredit(float amountOfCredit) { 28 | this.amountOfCredit = amountOfCredit; 29 | } 30 | 31 | public Customer getCustomer() { 32 | return customer; 33 | } 34 | 35 | public int getCreditNumber() { 36 | return creditNumber; 37 | } 38 | 39 | public Status getStatus() { 40 | return status; 41 | } 42 | 43 | public void setStatus(Status status) { 44 | this.status = status; 45 | } 46 | 47 | public CreditAccount getAccount() { 48 | return account; 49 | } 50 | 51 | public void setAccount(CreditAccount account) { 52 | this.account = account; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/main/java/de/wps/ddd/banking/models/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | public class CreditAccount extends Account { 4 | private Credit credit; 5 | 6 | public CreditAccount(int accountNumber, Credit credit) { 7 | super(accountNumber, credit.getCustomer()); 8 | this.setBalance(-(credit.getAmountOfCredit())); 9 | this.credit = credit; 10 | 11 | } 12 | 13 | public Credit getCredit() { 14 | return credit; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/main/java/de/wps/ddd/banking/models/Customer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | import java.time.LocalDate; 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Customer { 7 | private String firstName; 8 | private String familyName; 9 | private LocalDate dateOfBirth; 10 | private int customerNumber; 11 | private List accountList; 12 | private List creditList; 13 | 14 | public Customer(String firstName, String familyName, LocalDate dateOfBirth, int customerNumber) { 15 | super(); 16 | this.firstName = firstName; 17 | this.familyName = familyName; 18 | this.dateOfBirth = dateOfBirth; 19 | this.customerNumber = customerNumber; 20 | accountList = new ArrayList(); 21 | creditList = new ArrayList(); 22 | } 23 | 24 | public String getFirstName() { 25 | return firstName; 26 | } 27 | 28 | public String getFamilyName() { 29 | return familyName; 30 | } 31 | 32 | public LocalDate getDateOfBirth() { 33 | return dateOfBirth; 34 | } 35 | 36 | public int getCustomerNumber() { 37 | return customerNumber; 38 | } 39 | 40 | public List getAccountList() { 41 | return accountList; 42 | } 43 | 44 | public List getCreditList() { 45 | return creditList; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/test/java/de/wps/ddd/banking/models/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AccountTest { 10 | 11 | @Test 12 | void testAccountConstruction() { 13 | 14 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 15 | Account account = new Account(10, accountowner); 16 | assertEquals(10, account.getAccountnumber()); 17 | assertEquals(0, account.getBalance()); 18 | assertEquals(accountowner, account.getAccountowner()); 19 | } 20 | 21 | @Test 22 | void testBalanceAccount() { 23 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 24 | Account account = new Account(10, accountowner); 25 | assertEquals(0, account.getBalance()); 26 | account.setBalance(100); 27 | assertEquals(100, account.getBalance()); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/test/java/de/wps/ddd/banking/models/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditTest { 11 | 12 | @Test 13 | void testCreditConstruction() { 14 | 15 | Customer customer = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | Credit credit = new Credit(12, customer, 1000); 17 | assertEquals(1000, credit.getAmountOfCredit()); 18 | assertEquals(customer, credit.getCustomer()); 19 | assertEquals(12, credit.getCreditNumber()); 20 | assertTrue(customer.getCreditList().contains(credit)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /1 Hands-on Legacy Code/src/test/java/de/wps/ddd/banking/models/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.models; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | Customer customer = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(11, customer.getCustomerNumber()); 20 | assertNotNull(customer.getAccountList()); 21 | assertNotNull(customer.getCreditList()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=21 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 14 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 15 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 16 | org.eclipse.jdt.core.compiler.release=disabled 17 | org.eclipse.jdt.core.compiler.source=21 18 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/bounded-contexts-without-mapping.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/bounded-contexts-without-mapping.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnBoundedContextsWithoutMapping 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | } 6 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/bounded-contexts-without-mapping.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnBoundedContextsWithoutMapping 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | } 10 | 11 | artifact Credit 12 | { 13 | include "**/credit/**" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/bounded-contexts-without-mapping.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Thu Mar 07 12:26:33 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville 4 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/bounded-contexts-without-mapping.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | de.wps.ddd.banking 8 | maven-parent 9 | 1.0-SNAPSHOT 10 | ../maven-parent 11 | 12 | 13 | bounded-contexts-without-mapping 14 | 2 Hands-on Bounded Contexts without Mapping 15 | 16 | 17 | 18 | org.junit.jupiter 19 | junit-jupiter-api 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-params 24 | 25 | 26 | org.assertj 27 | assertj-core 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | com.hello2morrow 40 | sonargraph-maven-plugin 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | public class Account { 4 | private float balance; 5 | private int accountNumber; 6 | private Customer accountOwner; 7 | 8 | public Account(int accountNumber, Customer accountOwner) { 9 | super(); 10 | this.balance = 0; 11 | this.accountNumber = accountNumber; 12 | this.accountOwner = accountOwner; 13 | } 14 | 15 | public float getBalance() { 16 | return balance; 17 | } 18 | 19 | public void setBalance(float balance) { 20 | this.balance = balance; 21 | } 22 | 23 | public int getAccountnumber() { 24 | return accountNumber; 25 | } 26 | 27 | public Customer getAccountowner() { 28 | return accountOwner; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/main/java/de/wps/ddd/banking/accounting/Customer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | import java.time.LocalDate; 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | 7 | public class Customer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private int customerNumber; 12 | private List accountList; 13 | 14 | public Customer(String firstName, String familyName, LocalDate dateOfBirth, int customerNumber) { 15 | super(); 16 | this.firstName = firstName; 17 | this.familyName = familyName; 18 | this.dateOfBirth = dateOfBirth; 19 | this.customerNumber = customerNumber; 20 | accountList = new ArrayList(); 21 | } 22 | 23 | public String getFirstName() { 24 | return firstName; 25 | } 26 | 27 | public String getFamilyName() { 28 | return familyName; 29 | } 30 | 31 | public LocalDate getDateOfBirth() { 32 | return dateOfBirth; 33 | } 34 | 35 | public int getCustomerNumber() { 36 | return customerNumber; 37 | } 38 | 39 | public List getAccountList() { 40 | return accountList; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class Credit { 4 | private float amountOfCredit; 5 | private int creditNumber; 6 | private Status status; 7 | private CreditCustomer customer; 8 | private CreditAccount account; 9 | 10 | public enum Status { 11 | applied, refused, granted, delayed, payed 12 | } 13 | 14 | public Credit(int creditNumber, CreditCustomer customer, float amountOfCredit) { 15 | super(); 16 | this.amountOfCredit = amountOfCredit; 17 | this.creditNumber = creditNumber; 18 | this.customer = customer; 19 | this.customer.getCreditList().add(this); 20 | this.status = Status.applied; 21 | } 22 | 23 | public float getAmountOfCredit() { 24 | return amountOfCredit; 25 | } 26 | 27 | public void setAmountOfCredit(float amountOfCredit) { 28 | this.amountOfCredit = amountOfCredit; 29 | } 30 | 31 | public CreditCustomer getCustomer() { 32 | return customer; 33 | } 34 | 35 | public int getCreditNumber() { 36 | return creditNumber; 37 | } 38 | 39 | public Status getStatus() { 40 | return status; 41 | } 42 | 43 | public void setStatus(Status status) { 44 | this.status = status; 45 | } 46 | 47 | public CreditAccount getAccount() { 48 | return account; 49 | } 50 | 51 | public void setAccount(CreditAccount account) { 52 | this.account = account; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class CreditAccount { 4 | private float balance; 5 | private int accountNumber; 6 | private CreditCustomer accountOwner; 7 | private Credit credit; 8 | 9 | public CreditAccount(int accountNumber, Credit credit) { 10 | super(); 11 | this.balance = -credit.getAmountOfCredit(); 12 | this.accountNumber = accountNumber; 13 | this.credit = credit; 14 | this.accountOwner = credit.getCustomer(); 15 | } 16 | 17 | public float getBalance() { 18 | return balance; 19 | } 20 | 21 | public int getAccountnumber() { 22 | return accountNumber; 23 | } 24 | 25 | public void setBalance(float amount) { 26 | this.balance = amount; 27 | } 28 | 29 | public CreditCustomer getAccountowner() { 30 | return accountOwner; 31 | } 32 | 33 | public Credit getCredit() { 34 | return credit; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/main/java/de/wps/ddd/banking/credit/CreditCustomer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class CreditCustomer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private int customerNumber; 12 | private List accountList; 13 | private List creditList; 14 | 15 | public CreditCustomer(String firstName, String familyName, LocalDate dateOfBirth, int customerNumber) { 16 | super(); 17 | this.firstName = firstName; 18 | this.familyName = familyName; 19 | this.dateOfBirth = dateOfBirth; 20 | this.customerNumber = customerNumber; 21 | accountList = new ArrayList(); 22 | creditList = new ArrayList(); 23 | } 24 | 25 | public String getFirstName() { 26 | return firstName; 27 | } 28 | 29 | public String getFamilyName() { 30 | return familyName; 31 | } 32 | 33 | public LocalDate getDateOfBirth() { 34 | return dateOfBirth; 35 | } 36 | 37 | public int getCustomerNumber() { 38 | return customerNumber; 39 | } 40 | 41 | public List getAccountList() { 42 | return accountList; 43 | } 44 | 45 | public List getCreditList() { 46 | return creditList; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AccountTest { 10 | 11 | @Test 12 | void testAccountConstruction() { 13 | 14 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 15 | Account account = new Account(10, accountowner); 16 | assertEquals(10, account.getAccountnumber()); 17 | assertEquals(0, account.getBalance()); 18 | assertEquals(accountowner, account.getAccountowner()); 19 | } 20 | 21 | @Test 22 | void testBalanceAccount() { 23 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 24 | Account account = new Account(10, accountowner); 25 | assertEquals(0, account.getBalance()); 26 | account.setBalance(100); 27 | assertEquals(100, account.getBalance()); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | Customer customer = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(11, customer.getCustomerNumber()); 20 | assertNotNull(customer.getAccountList()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditCustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(11, customer.getCustomerNumber()); 20 | assertNotNull(customer.getAccountList()); 21 | assertNotNull(customer.getCreditList()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts without Mapping/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditTest { 11 | 12 | @Test 13 | void testCreditConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | Credit credit = new Credit(12, customer, 1000); 17 | customer.getCreditList().add(credit); 18 | assertEquals(1000, credit.getAmountOfCredit()); 19 | assertEquals(12, credit.getCreditNumber()); 20 | assertTrue(customer.getCreditList().contains(credit)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=21 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 14 | org.eclipse.jdt.core.compiler.release=enabled 15 | org.eclipse.jdt.core.compiler.source=21 16 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/bounded-contexts.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/bounded-contexts.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnBoundedContexts 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/bounded-contexts.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnBoundedContexts 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | 10 | // Allow dependencies from Accounting to Credit 11 | connect to Credit 12 | } 13 | 14 | artifact Credit 15 | { 16 | include "**/credit/**" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/bounded-contexts.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Thu Mar 07 11:32:38 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville 4 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/bounded-contexts.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | de.wps.ddd.banking 8 | maven-parent 9 | 1.0-SNAPSHOT 10 | ../maven-parent 11 | 12 | 13 | bounded-contexts 14 | 2 Hands-on Bounded Contexts 15 | 16 | 17 | 18 | org.junit.jupiter 19 | junit-jupiter-api 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-params 24 | 25 | 26 | org.assertj 27 | assertj-core 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | com.hello2morrow 40 | sonargraph-maven-plugin 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | public class Account { 4 | private float balance; 5 | private int accountNumber; 6 | private Customer accountOwner; 7 | 8 | public Account(int accountNumber, Customer accountOwner) { 9 | super(); 10 | this.balance = 0; 11 | this.accountNumber = accountNumber; 12 | this.accountOwner = accountOwner; 13 | } 14 | 15 | public float getBalance() { 16 | return balance; 17 | } 18 | 19 | public void setBalance(float balance) { 20 | this.balance = balance; 21 | } 22 | 23 | public int getAccountnumber() { 24 | return accountNumber; 25 | } 26 | 27 | public Customer getAccountowner() { 28 | return accountOwner; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/main/java/de/wps/ddd/banking/accounting/Customer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | import java.time.LocalDate; 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | 7 | public class Customer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private int customerNumber; 12 | private List accountList; 13 | 14 | public Customer(String firstName, String familyName, LocalDate dateOfBirth, int customerNumber) { 15 | super(); 16 | this.firstName = firstName; 17 | this.familyName = familyName; 18 | this.dateOfBirth = dateOfBirth; 19 | this.customerNumber = customerNumber; 20 | accountList = new ArrayList(); 21 | } 22 | 23 | public String getFirstName() { 24 | return firstName; 25 | } 26 | 27 | public String getFamilyName() { 28 | return familyName; 29 | } 30 | 31 | public LocalDate getDateOfBirth() { 32 | return dateOfBirth; 33 | } 34 | 35 | public int getCustomerNumber() { 36 | return customerNumber; 37 | } 38 | 39 | public List getAccountList() { 40 | return accountList; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class Credit { 4 | private float amountOfCredit; 5 | private int creditNumber; 6 | private Status status; 7 | private CreditCustomer customer; 8 | private CreditAccount account; 9 | 10 | public enum Status { 11 | applied, refused, granted, delayed, payed 12 | } 13 | 14 | public Credit(int creditNumber, CreditCustomer customer, float amountOfCredit) { 15 | super(); 16 | this.amountOfCredit = amountOfCredit; 17 | this.creditNumber = creditNumber; 18 | this.customer = customer; 19 | this.customer.getCreditList().add(this); 20 | this.status = Status.applied; 21 | } 22 | 23 | public float getAmountOfCredit() { 24 | return amountOfCredit; 25 | } 26 | 27 | public void setAmountOfCredit(float amountOfCredit) { 28 | this.amountOfCredit = amountOfCredit; 29 | } 30 | 31 | public CreditCustomer getCustomer() { 32 | return customer; 33 | } 34 | 35 | public int getCreditNumber() { 36 | return creditNumber; 37 | } 38 | 39 | public Status getStatus() { 40 | return status; 41 | } 42 | 43 | public void setStatus(Status status) { 44 | this.status = status; 45 | } 46 | 47 | public CreditAccount getAccount() { 48 | return account; 49 | } 50 | 51 | public void setAccount(CreditAccount account) { 52 | this.account = account; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class CreditAccount { 4 | private float balance; 5 | private int accountNumber; 6 | private CreditCustomer accountOwner; 7 | private Credit credit; 8 | 9 | public CreditAccount(int accountNumber, Credit credit) { 10 | super(); 11 | this.balance = -credit.getAmountOfCredit(); 12 | this.accountNumber = accountNumber; 13 | this.credit = credit; 14 | this.accountOwner = credit.getCustomer(); 15 | } 16 | 17 | public float getBalance() { 18 | return balance; 19 | } 20 | 21 | public int getAccountnumber() { 22 | return accountNumber; 23 | } 24 | 25 | public void setBalance(float amount) { 26 | this.balance = amount; 27 | } 28 | 29 | public CreditCustomer getAccountowner() { 30 | return accountOwner; 31 | } 32 | 33 | public Credit getCredit() { 34 | return credit; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/main/java/de/wps/ddd/banking/credit/CreditCustomer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class CreditCustomer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private int customerNumber; 12 | private List accountList; 13 | private List creditList; 14 | 15 | public CreditCustomer(String firstName, String familyName, LocalDate dateOfBirth, int customerNumber) { 16 | super(); 17 | this.firstName = firstName; 18 | this.familyName = familyName; 19 | this.dateOfBirth = dateOfBirth; 20 | this.customerNumber = customerNumber; 21 | accountList = new ArrayList(); 22 | creditList = new ArrayList(); 23 | } 24 | 25 | public String getFirstName() { 26 | return firstName; 27 | } 28 | 29 | public String getFamilyName() { 30 | return familyName; 31 | } 32 | 33 | public LocalDate getDateOfBirth() { 34 | return dateOfBirth; 35 | } 36 | 37 | public int getCustomerNumber() { 38 | return customerNumber; 39 | } 40 | 41 | public List getAccountList() { 42 | return accountList; 43 | } 44 | 45 | public List getCreditList() { 46 | return creditList; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AccountTest { 10 | 11 | @Test 12 | void testAccountConstruction() { 13 | 14 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 15 | Account account = new Account(10, accountowner); 16 | assertEquals(10, account.getAccountnumber()); 17 | assertEquals(0, account.getBalance()); 18 | assertEquals(accountowner, account.getAccountowner()); 19 | } 20 | 21 | @Test 22 | void testBalanceAccount() { 23 | Customer accountowner = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 24 | Account account = new Account(10, accountowner); 25 | assertEquals(0, account.getBalance()); 26 | account.setBalance(100); 27 | assertEquals(100, account.getBalance()); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | Customer customer = new Customer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(11, customer.getCustomerNumber()); 20 | assertNotNull(customer.getAccountList()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditCustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(11, customer.getCustomerNumber()); 20 | assertNotNull(customer.getAccountList()); 21 | assertNotNull(customer.getCreditList()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /2 Hands-on Bounded Contexts/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditTest { 11 | 12 | @Test 13 | void testCreditConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 11); 16 | Credit credit = new Credit(12, customer, 1000); 17 | customer.getCreditList().add(credit); 18 | assertEquals(1000, credit.getAmountOfCredit()); 19 | assertEquals(12, credit.getCreditNumber()); 20 | assertTrue(customer.getCreditList().contains(credit)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | de.wps.ddd.banking 8 | maven-parent 9 | 1.0-SNAPSHOT 10 | ../maven-parent 11 | 12 | 13 | value-objects-without-shared-kernel 14 | 3 Hands-on Value Objects without Shared Kernel 15 | 16 | 17 | 18 | de.wps.common 19 | common-contracts 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-api 24 | 25 | 26 | org.junit.jupiter 27 | junit-jupiter-params 28 | 29 | 30 | org.assertj 31 | assertj-core 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | com.hello2morrow 44 | sonargraph-maven-plugin 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | public class Account { 4 | private Amount balance; 5 | private AccountNumber accountNumber; 6 | private Customer accountOwner; 7 | 8 | public Account(Customer accountOwner, AccountNumber accountNumber) { 9 | super(); 10 | this.balance = Amount.of(0); 11 | this.accountNumber = accountNumber; 12 | this.accountOwner = accountOwner; 13 | } 14 | 15 | public Amount getBalance() { 16 | return balance; 17 | } 18 | 19 | public void setBalance(Amount amount) { 20 | this.balance = amount; 21 | } 22 | 23 | public AccountNumber getAccountnumber() { 24 | return accountNumber; 25 | } 26 | 27 | public Customer getAccountowner() { 28 | return accountOwner; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import de.wps.ddd.banking.credit.CreditNumber; 6 | import java.util.Objects; 7 | 8 | /** 9 | * ValueObject, representing a syntactically valid account number 10 | * 11 | *

Implemented as a class with:

12 | *
    13 | *
  • isValid method to check for validity
  • 14 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 15 | *
  • equals/hashCode based on the internal int value
  • 16 | *
17 | * 18 | * @see CustomerNumber for an alternative way of implementing value objects 19 | * @see CreditNumber for an alternative way of implementing value objects 20 | */ 21 | public class AccountNumber { 22 | 23 | public static boolean isValid(int accountNumberValue) { 24 | return accountNumberValue > 0; 25 | } 26 | 27 | public static AccountNumber of(int accountNumberValue) { 28 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 29 | return new AccountNumber(accountNumberValue); 30 | } 31 | 32 | private final int accountNumberValue; 33 | 34 | private AccountNumber(int accountNumberValue) { 35 | this.accountNumberValue = accountNumberValue; 36 | } 37 | 38 | public int valueInt() { 39 | return this.accountNumberValue; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | AccountNumber that = (AccountNumber) o; 47 | return accountNumberValue == that.accountNumberValue; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hash(accountNumberValue); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link AccountNumber}s. 9 | */ 10 | public class AccountNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public AccountNumber newAccountNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return AccountNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 23 | requireNotNull(accountNumber, "accountNumber"); 24 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import de.wps.ddd.banking.credit.CreditNumber; 4 | import java.util.Objects; 5 | 6 | /** 7 | * ValueObject, representing a syntactically valid amount 8 | * 9 | *

Implemented as a class with:

10 | *
    11 | *
  • isValid method to check for validity
  • 12 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * 16 | * @see CustomerNumber for an alternative way of implementing value objects 17 | * @see CreditNumber for an alternative way of implementing value objects 18 | */ 19 | public class Amount { 20 | 21 | public static boolean isValidAmount(float amount) { 22 | // All float values are considered valid 23 | return true; 24 | } 25 | 26 | public static Amount of(float amount) { 27 | return new Amount(amount); 28 | } 29 | 30 | private final float amount; 31 | 32 | private Amount(float amount) { 33 | this.amount = amount; 34 | } 35 | 36 | public Amount add(Amount secondAmount) { 37 | return of(this.amount + secondAmount.amount); 38 | } 39 | 40 | public Amount subtract(Amount secondAmount) { 41 | return of(this.amount - secondAmount.amount); 42 | } 43 | 44 | public float value() { 45 | return this.amount; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (o == null || getClass() != o.getClass()) return false; 52 | Amount amount1 = (Amount) o; 53 | return Float.compare(amount, amount1.amount) == 0; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(amount); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/Customer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class Customer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private CustomerNumber customerNumber; 12 | private List accountList; 13 | 14 | public Customer(CustomerNumber customerNumber, String firstName, String familyName, LocalDate dateOfBirth) { 15 | super(); 16 | this.firstName = firstName; 17 | this.familyName = familyName; 18 | this.dateOfBirth = dateOfBirth; 19 | this.customerNumber = customerNumber; 20 | accountList = new ArrayList<>(); 21 | } 22 | 23 | public String getFirstName() { 24 | return firstName; 25 | } 26 | 27 | public String getFamilyName() { 28 | return familyName; 29 | } 30 | 31 | public LocalDate getDateOfBirth() { 32 | return dateOfBirth; 33 | } 34 | 35 | public CustomerNumber getCustomerNumber() { 36 | return customerNumber; 37 | } 38 | 39 | public List getAccountList() { 40 | return accountList; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see de.wps.ddd.banking.credit.CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CustomerNumber}s. 9 | */ 10 | public class CustomerNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CustomerNumber newCustomerNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return new CustomerNumber(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 23 | requireNotNull(CustomerNumber, "CustomerNumber"); 24 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid account number 9 | * 10 | *

Implemented as a class with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 14 | *
  • equals/hashCode based on the internal int value
  • 15 | *
16 | * 17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see CreditNumber for an alternative way of implementing value objects 19 | */ 20 | public class AccountNumber { 21 | 22 | public static boolean isValid(int accountNumberValue) { 23 | return accountNumberValue > 0; 24 | } 25 | 26 | public static AccountNumber of(int accountNumberValue) { 27 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 28 | return new AccountNumber(accountNumberValue); 29 | } 30 | 31 | private final int accountNumberValue; 32 | 33 | private AccountNumber(int accountNumberValue) { 34 | this.accountNumberValue = accountNumberValue; 35 | } 36 | 37 | public int valueInt() { 38 | return this.accountNumberValue; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | AccountNumber that = (AccountNumber) o; 46 | return accountNumberValue == that.accountNumberValue; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(accountNumberValue); 52 | } 53 | } -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.util.Objects; 4 | 5 | public class Amount { 6 | 7 | public static boolean isValidAmount(float amount) { 8 | return (amount >= 0); 9 | } 10 | 11 | public static Amount of(float amount) { 12 | return new Amount(amount); 13 | } 14 | 15 | private final float amount; 16 | 17 | private Amount(float amount) { 18 | this.amount = amount; 19 | } 20 | 21 | public Amount add(Amount secondAmount) { 22 | return of(this.amount + secondAmount.amount); 23 | } 24 | 25 | public Amount subtract(Amount secondAmount) { 26 | return of(this.amount - secondAmount.amount); 27 | } 28 | 29 | public float value() { 30 | return this.amount; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (o == null || getClass() != o.getClass()) return false; 37 | Amount amount1 = (Amount) o; 38 | return Float.compare(amount, amount1.amount) == 0; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return Objects.hash(amount); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class Credit { 4 | private Amount amountOfCredit; 5 | private CreditNumber creditNumber; 6 | private Status status; 7 | private CreditCustomer customer; 8 | private CreditAccount account; 9 | 10 | public enum Status { 11 | applied, refused, granted, delayed, payed 12 | } 13 | 14 | public Credit(CreditNumber creditNumber, CreditCustomer customer, Amount amountOfCredit) { 15 | super(); 16 | this.customer = customer; 17 | this.amountOfCredit = amountOfCredit; 18 | this.creditNumber = creditNumber; 19 | this.status = Status.applied; 20 | } 21 | 22 | public Amount getAmountOfCredit() { 23 | return amountOfCredit; 24 | } 25 | 26 | public void setAmountOfCredit(Amount amountOfCredit) { 27 | this.amountOfCredit = amountOfCredit; 28 | } 29 | 30 | public CreditCustomer getCustomer() { 31 | return customer; 32 | } 33 | 34 | public CreditNumber getCreditNumber() { 35 | return creditNumber; 36 | } 37 | 38 | public Status getStatus() { 39 | return status; 40 | } 41 | 42 | public void setStatus(Status status) { 43 | this.status = status; 44 | } 45 | 46 | public CreditAccount getAccount() { 47 | return account; 48 | } 49 | 50 | public void setAccount(CreditAccount account) { 51 | this.account = account; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | public class CreditAccount { 4 | private Amount balance; 5 | private AccountNumber accountNumber; 6 | private CreditCustomer accountOwner; 7 | private Credit credit; 8 | 9 | public CreditAccount(Credit credit, AccountNumber accountNumber) { 10 | super(); 11 | this.credit = credit; 12 | this.balance = Amount.of(0).subtract(this.credit.getAmountOfCredit()); 13 | this.accountNumber = accountNumber; 14 | this.accountOwner = credit.getCustomer(); 15 | } 16 | 17 | public Amount getBalance() { 18 | return balance; 19 | } 20 | 21 | public AccountNumber getAccountnumber() { 22 | return accountNumber; 23 | } 24 | 25 | public void setBalance(Amount amount) { 26 | this.balance = amount; 27 | } 28 | 29 | public CreditCustomer getAccountowner() { 30 | return accountOwner; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditCustomer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class CreditCustomer { 8 | private String firstName; 9 | private String familyName; 10 | private LocalDate dateOfBirth; 11 | private CustomerNumber customerNumber; 12 | private List accountList; 13 | private List creditList; 14 | 15 | public CreditCustomer(String firstName, String familyName, LocalDate dateOfBirth, CustomerNumber number) { 16 | super(); 17 | this.firstName = firstName; 18 | this.familyName = familyName; 19 | this.dateOfBirth = dateOfBirth; 20 | this.customerNumber = number; 21 | accountList = new ArrayList<>(); 22 | creditList = new ArrayList<>(); 23 | } 24 | 25 | public String getFirstName() { 26 | return firstName; 27 | } 28 | 29 | public String getFamilyName() { 30 | return familyName; 31 | } 32 | 33 | public LocalDate getDateOfBirth() { 34 | return dateOfBirth; 35 | } 36 | 37 | public CustomerNumber getCustomerNumber() { 38 | return customerNumber; 39 | } 40 | 41 | public List getAccountList() { 42 | return accountList; 43 | } 44 | 45 | public List getCreditList() { 46 | return creditList; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject representing a syntactically valid credit numbers 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 12 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see AccountNumber for an alternative way of implementing value objects 17 | */ 18 | public record CreditNumber(int creditNumberValue) { 19 | 20 | public static boolean isValid(int creditNumberValue) { 21 | return creditNumberValue > 0; 22 | } 23 | public static CreditNumber of(int creditNumberValue) { 24 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 25 | return new CreditNumber(creditNumberValue); 26 | } 27 | 28 | public int value() { 29 | return creditNumberValue; 30 | } 31 | } -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CreditNumber}s. 9 | */ 10 | public class CreditNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CreditNumber newCreditNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return CreditNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 23 | requireNotNull(creditNumber, "creditNumber"); 24 | return creditNumber.value() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AccountTest { 10 | 11 | public static final AccountNumber ACCOUNT_NUMBER = AccountNumber.of(1); 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 13 | 14 | @Test 15 | void testAccountConstruction() { 16 | 17 | Customer accountowner = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 18 | Account account = new Account(accountowner, ACCOUNT_NUMBER); 19 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 20 | assertEquals(0, account.getBalance().value()); 21 | assertEquals(accountowner, account.getAccountowner()); 22 | } 23 | 24 | @Test 25 | void testBalanceAccount() { 26 | Customer accountOwner = new Customer(CUSTOMER_NUMBER,"Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 27 | Account account = new Account(accountOwner, ACCOUNT_NUMBER); 28 | assertEquals(0, account.getBalance().value()); 29 | account.setBalance(Amount.of(100)); 30 | assertEquals(100, account.getBalance().value()); 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/accounting/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertTrue(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertTrue(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubstract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 13 | 14 | @Test 15 | void testCustomerConstruction() { 16 | 17 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 18 | assertEquals("Carola", customer.getFirstName()); 19 | assertEquals("Lilienthal", customer.getFamilyName()); 20 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 21 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 22 | assertNotNull(customer.getAccountList()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/credit/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertFalse(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertFalse(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubstract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditCustomerTest { 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 16 | new CustomerNumber(5)); 17 | assertEquals("Carola", customer.getFirstName()); 18 | assertEquals("Lilienthal", customer.getFamilyName()); 19 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 20 | assertNotNull(customer.getCustomerNumber()); 21 | assertNotNull(customer.getAccountList()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditTest { 11 | 12 | public static final CreditNumber CREDIT_NUMBER = CreditNumber.of(7); 13 | 14 | @Test 15 | void testCreditConstruction() { 16 | 17 | CreditCustomer customer = new CreditCustomer("Carola", "Lilienthal", LocalDate.of(1967, 9, 11), 18 | new CustomerNumber(5)); 19 | Credit credit = new Credit(CREDIT_NUMBER, customer, Amount.of(1000)); 20 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 21 | assertEquals(CREDIT_NUMBER, credit.getCreditNumber()); 22 | 23 | customer.getCreditList().add(credit); 24 | assertTrue(customer.getCreditList().contains(credit)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/value-objects-without-shared-kernel.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/value-objects-without-shared-kernel.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnValueObjectsWithoutSharedKernel 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/value-objects-without-shared-kernel.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnValueObjectsWithoutSharedKernel 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | 10 | // Connector containing only classes with names ending with Service 11 | connector Services 12 | { 13 | include "**Service" 14 | } 15 | 16 | // Allow only dependencies from services of Accounting to services of Credit 17 | connect Services to Credit.Services 18 | } 19 | 20 | artifact Credit 21 | { 22 | include "**/credit/**" 23 | 24 | // Interface containing only classes with names ending with Service 25 | interface Services 26 | { 27 | include "**Service" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/value-objects-without-shared-kernel.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Thu Mar 07 22:04:12 CET 2024 3 | $Authors$=CarolaLilienthal,Remy Sanlaville 4 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects without sharedKernel/value-objects-without-shared-kernel.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 4 | import de.wps.ddd.banking.sharedKernel.Amount; 5 | 6 | public class Account { 7 | private final AccountNumber accountNumber; 8 | private final Customer accountOwner; 9 | private Amount balance; 10 | 11 | public Account(Customer accountOwner, AccountNumber accountNumber) { 12 | this.accountNumber = accountNumber; 13 | this.accountOwner = accountOwner; 14 | this.balance = Amount.of(0); 15 | } 16 | 17 | public Amount getBalance() { 18 | return balance; 19 | } 20 | 21 | public void setBalance(Amount amount) { 22 | this.balance = amount; 23 | } 24 | 25 | public AccountNumber getAccountnumber() { 26 | return accountNumber; 27 | } 28 | 29 | public Customer getAccountowner() { 30 | return accountOwner; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/accounting/Customer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 8 | 9 | public class Customer { 10 | private String firstName; 11 | private String familyName; 12 | private LocalDate dateOfBirth; 13 | private CustomerNumber customerNumber; 14 | private List accountList; 15 | 16 | public Customer(CustomerNumber customerNumber, String firstName, String familyName, LocalDate dateOfBirth) { 17 | super(); 18 | this.firstName = firstName; 19 | this.familyName = familyName; 20 | this.dateOfBirth = dateOfBirth; 21 | this.customerNumber = customerNumber; 22 | accountList = new ArrayList<>(); 23 | } 24 | 25 | public String getFirstName() { 26 | return firstName; 27 | } 28 | 29 | public String getFamilyName() { 30 | return familyName; 31 | } 32 | 33 | public LocalDate getDateOfBirth() { 34 | return dateOfBirth; 35 | } 36 | 37 | public CustomerNumber getCustomerNumber() { 38 | return customerNumber; 39 | } 40 | 41 | public List getAccountList() { 42 | return accountList; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import de.wps.ddd.banking.sharedKernel.Amount; 4 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 5 | 6 | public class Credit { 7 | private final CreditNumber creditNumber; 8 | private final CreditCustomer customer; 9 | private final Amount amountOfCredit; 10 | private Status status; 11 | private CreditAccount account; 12 | 13 | public enum Status { 14 | applied, refused, granted, delayed, payed 15 | } 16 | 17 | public Credit(CreditCustomer customer, CreditNumber creditNumber, Amount amountOfCredit) { 18 | super(); 19 | this.customer = customer; 20 | this.amountOfCredit = amountOfCredit; 21 | this.creditNumber = creditNumber; 22 | this.status = Status.applied; 23 | } 24 | 25 | public Amount getAmountOfCredit() { 26 | return amountOfCredit; 27 | } 28 | 29 | 30 | 31 | public CreditCustomer getCustomer() { 32 | return customer; 33 | } 34 | 35 | public CreditNumber getCreditNumber() { 36 | return creditNumber; 37 | } 38 | 39 | public Status getStatus() { 40 | return status; 41 | } 42 | 43 | public void setStatus(Status status) { 44 | this.status = status; 45 | } 46 | 47 | public CreditAccount getAccount() { 48 | return account; 49 | } 50 | 51 | public void setAccount(CreditAccount account) { 52 | this.account = account; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 4 | import de.wps.ddd.banking.sharedKernel.Amount; 5 | 6 | public class CreditAccount { 7 | private Amount balance; 8 | private final AccountNumber accountNumber; 9 | private final CreditCustomer accountOwner; 10 | 11 | public CreditAccount(Credit credit, AccountNumber accountNumber) { 12 | super(); 13 | this.accountNumber = accountNumber; 14 | this.accountOwner = credit.getCustomer(); 15 | this.balance = Amount.of(0).subtract(credit.getAmountOfCredit()); 16 | } 17 | 18 | public Amount getBalance() { 19 | return balance; 20 | } 21 | 22 | public AccountNumber getAccountnumber() { 23 | return accountNumber; 24 | } 25 | 26 | public void setBalance(Amount amount) { 27 | this.balance = amount; 28 | } 29 | 30 | public CreditCustomer getAccountOwner() { 31 | return accountOwner; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/credit/CreditCustomer.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 8 | 9 | public class CreditCustomer { 10 | private String firstName; 11 | private String familyName; 12 | private LocalDate dateOfBirth; 13 | private CustomerNumber customerNumber; 14 | private List accountList; 15 | private List creditList; 16 | 17 | public CreditCustomer(CustomerNumber customerNumber, String firstName, String familyName, LocalDate dateOfBirth) { 18 | super(); 19 | this.firstName = firstName; 20 | this.familyName = familyName; 21 | this.dateOfBirth = dateOfBirth; 22 | this.customerNumber = customerNumber; 23 | accountList = new ArrayList<>(); 24 | creditList = new ArrayList<>(); 25 | } 26 | 27 | public String getFirstName() { 28 | return firstName; 29 | } 30 | 31 | public String getFamilyName() { 32 | return familyName; 33 | } 34 | 35 | public LocalDate getDateOfBirth() { 36 | return dateOfBirth; 37 | } 38 | 39 | public CustomerNumber getCustomerNumber() { 40 | return customerNumber; 41 | } 42 | 43 | public List getAccountList() { 44 | return accountList; 45 | } 46 | 47 | public List getCreditList() { 48 | return creditList; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid account number 9 | * 10 | *

Implemented as a class with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 14 | *
  • equals/hashCode based on the internal int value
  • 15 | *
16 | * 17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see CreditNumber for an alternative way of implementing value objects 19 | */ 20 | public class AccountNumber { 21 | 22 | public static boolean isValid(int accountNumberValue) { 23 | return accountNumberValue > 0; 24 | } 25 | 26 | public static AccountNumber of(int accountNumberValue) { 27 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 28 | return new AccountNumber(accountNumberValue); 29 | } 30 | 31 | private final int accountNumberValue; 32 | 33 | private AccountNumber(int accountNumberValue) { 34 | this.accountNumberValue = accountNumberValue; 35 | } 36 | 37 | public int valueInt() { 38 | return this.accountNumberValue; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | AccountNumber that = (AccountNumber) o; 46 | return accountNumberValue == that.accountNumberValue; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(accountNumberValue); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link AccountNumber}s. 9 | */ 10 | public class AccountNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public AccountNumber newAccountNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return AccountNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 23 | requireNotNull(accountNumber, "accountNumber"); 24 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid amount 7 | * 8 | *

Implemented as a class with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 12 | *
  • equals/hashCode based on the internal int value
  • 13 | *
14 | * 15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditNumber for an alternative way of implementing value objects 17 | */ 18 | public class Amount { 19 | 20 | public static boolean isValidAmount(float amount) { 21 | // All float values are considered valid 22 | return true; 23 | } 24 | 25 | public static Amount of(float amount) { 26 | return new Amount(amount); 27 | } 28 | 29 | private final float amount; 30 | 31 | private Amount(float amount) { 32 | this.amount = amount; 33 | } 34 | 35 | public Amount add(Amount secondAmount) { 36 | return of(this.amount + secondAmount.amount); 37 | } 38 | 39 | public Amount subtract(Amount secondAmount) { 40 | return of(this.amount - secondAmount.amount); 41 | } 42 | 43 | public float value() { 44 | return this.amount; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | Amount amount1 = (Amount) o; 52 | return Float.compare(amount, amount1.amount) == 0; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(amount); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject representing a syntactically valid credit numbers 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 12 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see AccountNumber for an alternative way of implementing value objects 17 | */ 18 | public record CreditNumber(int creditNumberValue) { 19 | 20 | public static boolean isValid(int creditNumberValue) { 21 | return creditNumberValue > 0; 22 | } 23 | public static CreditNumber of(int creditNumberValue) { 24 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 25 | return new CreditNumber(creditNumberValue); 26 | } 27 | 28 | public int value() { 29 | return creditNumberValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CreditNumber}s. 9 | */ 10 | public class CreditNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CreditNumber newCreditNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return CreditNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 23 | requireNotNull(creditNumber, "creditNumber"); 24 | return creditNumber.value() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CustomerNumber}s. 9 | */ 10 | public class CustomerNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CustomerNumber newCustomerNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return new CustomerNumber(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 23 | requireNotNull(CustomerNumber, "CustomerNumber"); 24 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 6 | import de.wps.ddd.banking.sharedKernel.AccountNumberFactory; 7 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 8 | import java.time.LocalDate; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | import de.wps.ddd.banking.sharedKernel.Amount; 13 | 14 | class AccountTest { 15 | 16 | private final static AccountNumber ACCOUNT_NUMBER = new AccountNumberFactory().newAccountNumber(); 17 | 18 | @Test 19 | void testAccountConstruction() { 20 | Customer accountOwner = new Customer(new CustomerNumber(1), "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 21 | Account account = new Account(accountOwner, ACCOUNT_NUMBER); 22 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 23 | assertEquals(0, account.getBalance().value()); 24 | assertEquals(accountOwner, account.getAccountowner()); 25 | } 26 | 27 | @Test 28 | void testBalanceAccount() { 29 | Customer accountOwner = new Customer(new CustomerNumber(1), "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 30 | Account account = new Account(accountOwner, ACCOUNT_NUMBER); 31 | assertEquals(0, account.getBalance().value()); 32 | account.setBalance(Amount.of(100)); 33 | assertEquals(100, account.getBalance().value()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 7 | import java.time.LocalDate; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | class CustomerTest { 12 | 13 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 14 | 15 | @Test 16 | void testCustomerConstruction() { 17 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 18 | assertEquals("Carola", customer.getFirstName()); 19 | assertEquals("Lilienthal", customer.getFamilyName()); 20 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 21 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 22 | assertNotNull(customer.getAccountList()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 7 | import java.time.LocalDate; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | class CreditCustomerTest { 12 | 13 | @Test 14 | void testCustomerConstruction() { 15 | 16 | CreditCustomer customer = new CreditCustomer(new CustomerNumber(1), "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 17 | assertEquals("Carola", customer.getFirstName()); 18 | assertEquals("Lilienthal", customer.getFamilyName()); 19 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 20 | assertNotNull(customer.getCustomerNumber()); 21 | assertNotNull(customer.getAccountList()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 8 | import de.wps.ddd.banking.sharedKernel.CreditNumberFactory; 9 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 10 | import java.time.LocalDate; 11 | 12 | import org.junit.jupiter.api.Test; 13 | 14 | import de.wps.ddd.banking.sharedKernel.Amount; 15 | 16 | class CreditTest { 17 | 18 | private final static CreditNumber CREDIT_NUMBER = new CreditNumberFactory().newCreditNumber(); 19 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 20 | 21 | @Test 22 | void testCreditConstruction() { 23 | 24 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 25 | Credit credit = new Credit(customer, CREDIT_NUMBER, Amount.of(1000)); 26 | assertEquals(CREDIT_NUMBER, credit.getCreditNumber()); 27 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 28 | assertNotNull(credit.getCreditNumber()); 29 | 30 | customer.getCreditList().add(credit); 31 | assertTrue(customer.getCreditList().contains(credit)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/sharedKernel/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertTrue(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertTrue(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubtract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/sharedKernel/CreditNumberFactoryTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class CreditNumberFactoryTest { 8 | 9 | @Test 10 | void isKnownCreditNumber() { 11 | CreditNumberFactory objectUnderTest = new CreditNumberFactory(); 12 | 13 | CreditNumber creditNumber = objectUnderTest.newCreditNumber(); 14 | assertNotNull(creditNumber); 15 | assertTrue(objectUnderTest.isKnownCreditNumber(creditNumber)); 16 | 17 | int maxCreditNumber = creditNumber.creditNumberValue(); 18 | assertFalse(objectUnderTest.isKnownCreditNumber(CreditNumber.of(maxCreditNumber + 1))); 19 | } 20 | } -------------------------------------------------------------------------------- /3 Hands-on Value Objects/src/test/java/de/wps/ddd/banking/sharedKernel/CreditNumberTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class CreditNumberTest { 8 | 9 | @Test 10 | void of() { 11 | CreditNumber creditNumber = CreditNumber.of(5); 12 | assertNotNull(creditNumber); 13 | assertEquals(5, creditNumber.creditNumberValue()); 14 | } 15 | @Test 16 | void ofInvalid() { 17 | assertThrows(IllegalArgumentException.class, () -> CreditNumber.of(0)); 18 | assertThrows(IllegalArgumentException.class, () -> CreditNumber.of(-1)); 19 | } 20 | 21 | @Test 22 | void testEquals() { 23 | CreditNumber creditNumber1 = CreditNumber.of(1); 24 | CreditNumber creditNumber2 = CreditNumber.of(1); 25 | CreditNumber creditNumber3 = CreditNumber.of(2); 26 | 27 | assertTrue(creditNumber1.equals(creditNumber1)); 28 | assertEquals(creditNumber1, creditNumber2); 29 | assertEquals(creditNumber2, creditNumber1); 30 | assertNotEquals(creditNumber1, creditNumber3); 31 | } 32 | } -------------------------------------------------------------------------------- /3 Hands-on Value Objects/value-objects.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/value-objects.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnValueObjects 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/value-objects.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact HandsOnValueObjects 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | 10 | // Allow dependencies from Accounting to Credit 11 | connect to Credit 12 | } 13 | 14 | artifact Credit 15 | { 16 | include "**/credit/**" 17 | } 18 | 19 | public artifact SharedKernel 20 | { 21 | include "**/sharedKernel/**" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/value-objects.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Thu Mar 07 12:52:42 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville,johannesrost 4 | -------------------------------------------------------------------------------- /3 Hands-on Value Objects/value-objects.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/.gitignore: -------------------------------------------------------------------------------- 1 | /.classpath 2 | /.project 3 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact RichDomainModel 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact RichDomainModel 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | 10 | // Allow dependencies from Accounting to Credit 11 | connect to Credit 12 | } 13 | 14 | artifact Credit 15 | { 16 | include "**/credit/**" 17 | } 18 | 19 | public artifact SharedKernel 20 | { 21 | include "**/sharedKernel/**" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Architecture/Pattern.arc: -------------------------------------------------------------------------------- 1 | artifact ValueObjectsPattern 2 | { 3 | exclude "External**" 4 | 5 | 6 | } -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Architecture/PatternCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact ValueObjectsPattern 2 | { 3 | exclude "External**" 4 | 5 | artifact Service 6 | { 7 | include "JavaHasAnnotation: org.jmolecules.ddd.annotation.Service" 8 | 9 | connect to Factory, Aggregate, Entity, ValueObject 10 | } 11 | 12 | artifact Factory 13 | { 14 | include "JavaHasAnnotation: org.jmolecules.ddd.annotation.Factory" 15 | 16 | connect to ValueObject 17 | } 18 | 19 | artifact Aggregate 20 | { 21 | include "JavaHasAnnotation: org.jmolecules.ddd.annotation.AggregateRoot" 22 | 23 | connect to Entity, ValueObject 24 | } 25 | 26 | artifact Entity 27 | { 28 | include "JavaHasAnnotation: org.jmolecules.ddd.annotation.Entity" 29 | 30 | connect to ValueObject 31 | } 32 | 33 | artifact ValueObject 34 | { 35 | include "JavaHasAnnotation: org.jmolecules.ddd.annotation.ValueObject" 36 | } 37 | } -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Fri Mar 08 11:58:54 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville,johannesrost 4 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/rich-domain-model.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 7 | import de.wps.ddd.banking.sharedKernel.Amount; 8 | import org.jmolecules.ddd.annotation.Entity; 9 | import org.jmolecules.ddd.annotation.Identity; 10 | 11 | @Entity 12 | public class Account { 13 | @Identity 14 | private final AccountNumber accountNumber; 15 | private final Customer accountOwner; 16 | private Amount balance; 17 | 18 | public Account(AccountNumber accountNumber, Customer accountOwner) { 19 | requireNotNull(accountNumber, "accountNumber"); 20 | requireNotNull(accountOwner, "accountOwner"); 21 | 22 | this.accountNumber = accountNumber; 23 | this.accountOwner = accountOwner; 24 | this.balance = Amount.of(0); 25 | } 26 | 27 | public Amount getBalance() { 28 | return balance; 29 | } 30 | 31 | public void withdraw(Amount amount) { 32 | requireNotNull(amount, "amount"); 33 | require(amount.isLessOrEquals(getBalance()), "amount.isLessOrEquals(getBalance())"); 34 | 35 | this.balance = this.balance.subtract(amount); 36 | } 37 | 38 | public void deposit(Amount amount) { 39 | requireNotNull(amount, "amount"); 40 | 41 | this.balance = this.balance.add(amount); 42 | } 43 | 44 | public AccountNumber getAccountnumber() { 45 | return accountNumber; 46 | } 47 | 48 | public Customer getAccountOwner() { 49 | return accountOwner; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.Amount; 7 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 8 | import java.util.Optional; 9 | import org.jmolecules.ddd.annotation.Entity; 10 | import org.jmolecules.ddd.annotation.Identity; 11 | 12 | @Entity 13 | public class Credit { 14 | @Identity 15 | private final CreditNumber creditNumber; 16 | private final Amount amountOfCredit; 17 | private final CreditCustomer customer; 18 | private CreditAccount account; 19 | private Status status; 20 | 21 | public enum Status { 22 | applied, refused, granted, delayed, payed 23 | } 24 | 25 | public Credit(CreditNumber creditNumber, CreditCustomer customer, Amount amountOfCredit) { 26 | requireNotNull(creditNumber, "creditNumber"); 27 | requireNotNull(customer, "customer"); 28 | requireNotNull(amountOfCredit, "amountOfCredit"); 29 | this.customer = customer; 30 | this.amountOfCredit = amountOfCredit; 31 | this.creditNumber = creditNumber; 32 | this.status = Status.applied; 33 | } 34 | 35 | public Amount getAmountOfCredit() { 36 | return amountOfCredit; 37 | } 38 | 39 | public CreditCustomer getCustomer() { 40 | return customer; 41 | } 42 | 43 | public CreditNumber getCreditNumber() { 44 | return creditNumber; 45 | } 46 | 47 | public Status getStatus() { 48 | return status; 49 | } 50 | 51 | public void grant(CreditAccount account) { 52 | requireNotNull(account, "account"); 53 | require(canBeGranted(), "canBeGranted()"); 54 | 55 | this.status = Status.granted; 56 | this.account = account; 57 | } 58 | 59 | public boolean canBeGranted() { 60 | return (this.status != Status.refused && this.status != Status.granted); 61 | } 62 | 63 | public Optional getAccount() { 64 | return Optional.ofNullable(account); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 6 | import de.wps.ddd.banking.sharedKernel.Amount; 7 | import org.jmolecules.ddd.annotation.Entity; 8 | import org.jmolecules.ddd.annotation.Identity; 9 | 10 | @Entity 11 | public class CreditAccount { 12 | @Identity 13 | private final AccountNumber accountNumber; 14 | private final CreditCustomer accountOwner; 15 | private final Credit credit; 16 | private Amount balance; 17 | 18 | public CreditAccount(AccountNumber accountNumber, Credit credit) { 19 | requireNotNull(accountNumber, "accountNumber"); 20 | requireNotNull(credit, "credit"); 21 | this.credit = credit; 22 | this.balance = Amount.of(0).subtract(credit.getAmountOfCredit()); 23 | this.accountNumber = accountNumber; 24 | this.accountOwner = credit.getCustomer(); 25 | } 26 | 27 | 28 | public Amount getBalance() { 29 | return balance; 30 | } 31 | 32 | public AccountNumber getAccountNumber() { 33 | return accountNumber; 34 | } 35 | 36 | public void deposit(Amount amount) { 37 | requireNotNull(amount, "amount"); 38 | this.balance = balance.add(amount); 39 | } 40 | 41 | public void withdraw(Amount amount) { 42 | requireNotNull(amount, "amount"); 43 | this.balance = balance.subtract(amount); 44 | } 45 | 46 | public CreditCustomer getAccountOwner() { 47 | return accountOwner; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | import org.jmolecules.ddd.annotation.ValueObject; 7 | 8 | /** 9 | * ValueObject, representing a syntactically valid account number 10 | * 11 | *

Implemented as a class with:

12 | *
    13 | *
  • isValid method to check for validity
  • 14 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 15 | *
  • equals/hashCode based on the internal int value
  • 16 | *
17 | * 18 | * @see CustomerNumber for an alternative way of implementing value objects 19 | * @see CreditNumber for an alternative way of implementing value objects 20 | */ 21 | @ValueObject 22 | public class AccountNumber { 23 | 24 | public static boolean isValid(int accountNumberValue) { 25 | return accountNumberValue > 0; 26 | } 27 | 28 | public static AccountNumber of(int accountNumberValue) { 29 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 30 | return new AccountNumber(accountNumberValue); 31 | } 32 | 33 | private final int accountNumberValue; 34 | 35 | private AccountNumber(int accountNumberValue) { 36 | this.accountNumberValue = accountNumberValue; 37 | } 38 | 39 | public int valueInt() { 40 | return this.accountNumberValue; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | AccountNumber that = (AccountNumber) o; 48 | return accountNumberValue == that.accountNumberValue; 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(accountNumberValue); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import org.jmolecules.ddd.annotation.Factory; 7 | 8 | /** 9 | * Factory to create {@link AccountNumber}s. 10 | */ 11 | @Factory 12 | public class AccountNumberFactory { 13 | 14 | /** 15 | * Normally this would be backed by some kind of persistence store 16 | */ 17 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 18 | 19 | public AccountNumber newAccountNumber() { 20 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 21 | return AccountNumber.of(nextFreeNumber); 22 | } 23 | 24 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 25 | requireNotNull(accountNumber, "accountNumber"); 26 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import java.util.Objects; 4 | import org.jmolecules.ddd.annotation.ValueObject; 5 | 6 | /** 7 | * ValueObject, representing a syntactically valid amount 8 | * 9 | *

Implemented as a class with:

10 | *
    11 | *
  • isValid method to check for validity
  • 12 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * 16 | * @see CustomerNumber for an alternative way of implementing value objects 17 | * @see CreditNumber for an alternative way of implementing value objects 18 | */ 19 | @ValueObject 20 | public class Amount { 21 | 22 | public static boolean isValidAmount(float amount) { 23 | // All float values are considered valid 24 | return true; 25 | } 26 | 27 | public static Amount of(float amount) { 28 | return new Amount(amount); 29 | } 30 | 31 | private final float amount; 32 | 33 | private Amount(float amount) { 34 | this.amount = amount; 35 | } 36 | 37 | public Amount add(Amount secondAmount) { 38 | return of(this.amount + secondAmount.amount); 39 | } 40 | 41 | public Amount subtract(Amount secondAmount) { 42 | return of(this.amount - secondAmount.amount); 43 | } 44 | 45 | public float value() { 46 | return this.amount; 47 | } 48 | 49 | public boolean isLessOrEquals(Amount amount) { 50 | return this.amount <= amount.value(); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | Amount amount1 = (Amount) o; 58 | return Float.compare(amount, amount1.amount) == 0; 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(amount); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import org.jmolecules.ddd.annotation.ValueObject; 6 | 7 | /** 8 | * ValueObject representing a syntactically valid credit numbers 9 | * 10 | *

Implemented as a record with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 14 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 15 | *
  • equals/hashCode based on the internal int value
  • 16 | *
17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see AccountNumber for an alternative way of implementing value objects 19 | */ 20 | @ValueObject 21 | public record CreditNumber(int creditNumberValue) { 22 | 23 | public static boolean isValid(int creditNumberValue) { 24 | return creditNumberValue > 0; 25 | } 26 | public static CreditNumber of(int creditNumberValue) { 27 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 28 | return new CreditNumber(creditNumberValue); 29 | } 30 | 31 | public int value() { 32 | return creditNumberValue; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import org.jmolecules.ddd.annotation.Factory; 7 | 8 | /** 9 | * Factory to create {@link CreditNumber}s. 10 | */ 11 | @Factory 12 | public class CreditNumberFactory { 13 | 14 | /** 15 | * Normally this would be backed by some kind of persistence store 16 | */ 17 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 18 | 19 | public CreditNumber newCreditNumber() { 20 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 21 | return CreditNumber.of(nextFreeNumber); 22 | } 23 | 24 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 25 | requireNotNull(creditNumber, "creditNumber"); 26 | return creditNumber.value() <= NUMBER_COUNTER.get(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import org.jmolecules.ddd.annotation.ValueObject; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid customer number 9 | * 10 | *

Implemented as a record with:

11 | *
    12 | *
  • isValid method to check validity
  • 13 | *
  • a public constructor directly coupled to the internal representation
  • 14 | *
  • validation implemented in the compact constructor
  • 15 | *
  • default method to access the internal representation
  • 16 | *
  • equals/hashCode automatically based on the internal int value
  • 17 | *
18 | * 19 | * @param customerNumberValue internal value of the customer number 20 | * @see CreditNumber 21 | * @see AccountNumber 22 | */ 23 | @ValueObject 24 | public record CustomerNumber(int customerNumberValue) { 25 | public CustomerNumber { 26 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 27 | } 28 | 29 | public static boolean isValid(int customerNumberValue) { 30 | return customerNumberValue > 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import org.jmolecules.ddd.annotation.Factory; 7 | 8 | /** 9 | * Factory to create {@link CustomerNumber}s. 10 | */ 11 | @Factory 12 | public class CustomerNumberFactory { 13 | 14 | /** 15 | * Normally this would be backed by some kind of persistence store 16 | */ 17 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 18 | 19 | public CustomerNumber newCustomerNumber() { 20 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 21 | return new CustomerNumber(nextFreeNumber); 22 | } 23 | 24 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 25 | requireNotNull(CustomerNumber, "CustomerNumber"); 26 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/RichDomainModelArchTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking; 2 | 3 | import static com.tngtech.archunit.library.Architectures.layeredArchitecture; 4 | 5 | import com.tngtech.archunit.core.importer.ImportOption; 6 | import com.tngtech.archunit.junit.AnalyzeClasses; 7 | import com.tngtech.archunit.junit.ArchTest; 8 | import com.tngtech.archunit.lang.ArchRule; 9 | import org.jmolecules.archunit.JMoleculesDddRules; 10 | 11 | @AnalyzeClasses(packagesOf = RichDomainModelArchTest.class, importOptions = { ImportOption.DoNotIncludeTests.class }) 12 | public class RichDomainModelArchTest { 13 | @ArchTest 14 | final ArchRule dddRules = JMoleculesDddRules.all(); 15 | 16 | @ArchTest 17 | final ArchRule boundedContexts = layeredArchitecture() 18 | .consideringOnlyDependenciesInAnyPackage(RichDomainModelArchTest.class.getPackageName() + "..") 19 | .as("Bounded contexts") 20 | .layer("accounting").definedBy("..accounting..") 21 | .layer("credit").definedBy("..credit..") 22 | .layer("sharedKernel").definedBy("..sharedKernel..") 23 | .whereLayer("accounting").mayNotBeAccessedByAnyLayer() 24 | .whereLayer("credit").mayOnlyBeAccessedByLayers("accounting") 25 | .whereLayer("sharedKernel").mayOnlyBeAccessedByLayers("accounting", "credit") 26 | .ensureAllClassesAreContainedInArchitecture(); 27 | } 28 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 6 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 7 | import java.time.LocalDate; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import de.wps.ddd.banking.sharedKernel.Amount; 12 | 13 | class AccountTest { 14 | 15 | public static final AccountNumber ACCOUNT_NUMBER = AccountNumber.of(1); 16 | 17 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(2); 18 | 19 | @Test 20 | void testAccountConstruction() { 21 | Customer accountOwner = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 22 | Account account = new Account(ACCOUNT_NUMBER, accountOwner); 23 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 24 | assertEquals(0, account.getBalance().value()); 25 | assertEquals(accountOwner.getCustomerNumber(), account.getAccountOwner().getCustomerNumber()); 26 | } 27 | 28 | @Test 29 | void testBalanceAccount() { 30 | Customer accountowner = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 31 | Account account = new Account(ACCOUNT_NUMBER, accountowner); 32 | assertEquals(0, account.getBalance().value()); 33 | account.deposit(Amount.of(100)); 34 | assertEquals(100, account.getBalance().value()); 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(2); 13 | @Test 14 | void testCustomerConstruction() { 15 | 16 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 17 | assertEquals("Carola", customer.getFirstName()); 18 | assertEquals("Lilienthal", customer.getFamilyName()); 19 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 20 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditCustomerTest { 11 | 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(2); 13 | 14 | @Test 15 | void testCustomerConstruction() { 16 | 17 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 18 | assertEquals("Carola", customer.getFirstName()); 19 | assertEquals("Lilienthal", customer.getFamilyName()); 20 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 21 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 9 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 10 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 11 | import java.time.LocalDate; 12 | 13 | import org.junit.jupiter.api.Test; 14 | 15 | import de.wps.ddd.banking.sharedKernel.Amount; 16 | 17 | class CreditTest { 18 | 19 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(2); 20 | 21 | public static final CreditNumber CREDIT_NUMBER = CreditNumber.of(7); 22 | 23 | 24 | 25 | @Test 26 | void testCreditConstruction() { 27 | 28 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 29 | Credit credit = new Credit(CREDIT_NUMBER, customer, Amount.of(1000)); 30 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 31 | assertNotNull(credit.getCreditNumber()); 32 | 33 | customer.addCredit(credit); 34 | assertTrue(customer.getCreditList().contains(credit)); 35 | assertTrue(credit.canBeGranted()); 36 | credit.grant(new CreditAccount(AccountNumber.of(3), credit)); 37 | assertFalse(credit.canBeGranted()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /4 Hands-on Rich domain model/src/test/java/de/wps/ddd/banking/sharedKernel/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertTrue(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertTrue(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubstract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/cycle-free-without-shared-kernel.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | public class Account { 7 | private final AccountNumber accountNumber; 8 | private Amount balance; 9 | 10 | public Account(AccountNumber accountNumber) { 11 | requireNotNull(accountNumber, "accountNumber"); 12 | 13 | this.accountNumber = accountNumber; 14 | this.balance = Amount.of(0); 15 | } 16 | 17 | public Amount getBalance() { 18 | return balance; 19 | } 20 | 21 | public void withdraw(Amount amount) { 22 | requireNotNull(amount, "amount"); 23 | require(amount.isLessOrEquals(getBalance()), "amount.isLessOrEquals(getBalance())"); 24 | 25 | this.balance = this.balance.subtract(amount); 26 | } 27 | 28 | public void deposit(Amount amount) { 29 | requireNotNull(amount, "amount"); 30 | 31 | this.balance = this.balance.add(amount); 32 | } 33 | 34 | public AccountNumber getAccountnumber() { 35 | return accountNumber; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid account number 9 | * 10 | *

Implemented as a class with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 14 | *
  • equals/hashCode based on the internal int value
  • 15 | *
16 | * 17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see de.wps.ddd.banking.credit.CreditNumber for an alternative way of implementing value objects 19 | */ 20 | public class AccountNumber { 21 | 22 | public static boolean isValid(int accountNumberValue) { 23 | return accountNumberValue > 0; 24 | } 25 | 26 | public static AccountNumber of(int accountNumberValue) { 27 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 28 | return new AccountNumber(accountNumberValue); 29 | } 30 | 31 | private final int accountNumberValue; 32 | 33 | private AccountNumber(int accountNumberValue) { 34 | this.accountNumberValue = accountNumberValue; 35 | } 36 | 37 | public int valueInt() { 38 | return this.accountNumberValue; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | AccountNumber that = (AccountNumber) o; 46 | return accountNumberValue == that.accountNumberValue; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(accountNumberValue); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link AccountNumber}s. 9 | */ 10 | public class AccountNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public AccountNumber newAccountNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return AccountNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 23 | requireNotNull(accountNumber, "accountNumber"); 24 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import de.wps.ddd.banking.credit.CreditNumber; 4 | import java.util.Objects; 5 | 6 | /** 7 | * ValueObject, representing a syntactically valid amount 8 | * 9 | *

Implemented as a class with:

10 | *
    11 | *
  • isValid method to check for validity
  • 12 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * 16 | * @see CustomerNumber for an alternative way of implementing value objects 17 | * @see CreditNumber for an alternative way of implementing value objects 18 | */ 19 | public class Amount { 20 | 21 | public static boolean isValidAmount(float amount) { 22 | // All float values are considered valid 23 | return true; 24 | } 25 | 26 | public static Amount of(float amount) { 27 | return new Amount(amount); 28 | } 29 | 30 | private final float amount; 31 | 32 | private Amount(float amount) { 33 | this.amount = amount; 34 | } 35 | 36 | public Amount add(Amount secondAmount) { 37 | return of(this.amount + secondAmount.amount); 38 | } 39 | 40 | public Amount subtract(Amount secondAmount) { 41 | return of(this.amount - secondAmount.amount); 42 | } 43 | 44 | public float value() { 45 | return this.amount; 46 | } 47 | 48 | public boolean isLessOrEquals(Amount amount) { 49 | return this.amount <= amount.value(); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) return true; 55 | if (o == null || getClass() != o.getClass()) return false; 56 | Amount amount1 = (Amount) o; 57 | return Float.compare(amount, amount1.amount) == 0; 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(amount); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see de.wps.ddd.banking.credit.CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/accounting/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import de.wps.ddd.banking.accounting.CustomerNumber; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | /** 9 | * Factory to create {@link CustomerNumber}s. 10 | */ 11 | public class CustomerNumberFactory { 12 | 13 | /** 14 | * Normally this would be backed by some kind of persistence store 15 | */ 16 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 17 | 18 | public CustomerNumber newCustomerNumber() { 19 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 20 | return new CustomerNumber(nextFreeNumber); 21 | } 22 | 23 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 24 | requireNotNull(CustomerNumber, "CustomerNumber"); 25 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid amount 7 | * 8 | *

Implemented as a class with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 12 | *
  • equals/hashCode based on the internal int value
  • 13 | *
14 | * 15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditNumber for an alternative way of implementing value objects 17 | */ 18 | public class Amount { 19 | 20 | public static boolean isValidAmount(float amount) { 21 | // All float values are considered valid 22 | return true; 23 | } 24 | 25 | public static Amount of(float amount) { 26 | return new Amount(amount); 27 | } 28 | 29 | private final float amount; 30 | 31 | private Amount(float amount) { 32 | this.amount = amount; 33 | } 34 | 35 | public Amount add(Amount secondAmount) { 36 | return of(this.amount + secondAmount.amount); 37 | } 38 | 39 | public Amount subtract(Amount secondAmount) { 40 | return of(this.amount - secondAmount.amount); 41 | } 42 | 43 | public float value() { 44 | return this.amount; 45 | } 46 | 47 | public boolean isLessOrEquals(Amount amount) { 48 | return this.amount <= amount.value(); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | Amount amount1 = (Amount) o; 56 | return Float.compare(amount, amount1.amount) == 0; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(amount); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import java.util.Optional; 7 | 8 | public class Credit { 9 | private final Amount amountOfCredit; 10 | private final CreditNumber creditNumber; 11 | private Status status; 12 | private CreditAccount account; 13 | 14 | public enum Status { 15 | applied, refused, granted, delayed, payed 16 | } 17 | 18 | public Credit(CreditNumber creditNumber, Amount amountOfCredit) { 19 | requireNotNull(creditNumber, "creditNumber"); 20 | requireNotNull(amountOfCredit, "amountOfCredit"); 21 | 22 | this.amountOfCredit = amountOfCredit; 23 | this.creditNumber = creditNumber; 24 | this.status = Status.applied; 25 | } 26 | 27 | public Amount getAmountOfCredit() { 28 | return amountOfCredit; 29 | } 30 | 31 | public CreditNumber getCreditNumber() { 32 | return creditNumber; 33 | } 34 | 35 | public Status getStatus() { 36 | return status; 37 | } 38 | 39 | public void grant(CreditAccount account) { 40 | requireNotNull(account, "account"); 41 | require(canBeGranted(), "canBeGranted()"); 42 | 43 | this.status = Status.granted; 44 | this.account = account; 45 | } 46 | 47 | public boolean canBeGranted() { 48 | return (this.status != Status.refused && this.status != Status.granted); 49 | } 50 | 51 | public Optional getAccount() { 52 | return Optional.ofNullable(account); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | 6 | public class CreditAccount { 7 | private final CreditAccountNumber creditAccountNumber; 8 | private Amount balance; 9 | 10 | 11 | public CreditAccount(CreditAccountNumber creditAccountNumber, Amount amountOfCredit) { 12 | requireNotNull(creditAccountNumber, "accountNumber"); 13 | requireNotNull(amountOfCredit, "amountOfCredit"); 14 | 15 | this.creditAccountNumber = creditAccountNumber; 16 | this.balance = Amount.of(0).subtract(amountOfCredit); 17 | } 18 | 19 | public Amount getBalance() { 20 | return balance; 21 | } 22 | 23 | public CreditAccountNumber getAccountNumber() { 24 | return creditAccountNumber; 25 | } 26 | 27 | public void deposit(Amount amount) { 28 | requireNotNull(amount, "amount"); 29 | this.balance = balance.add(amount); 30 | } 31 | 32 | public void withdraw(Amount amount) { 33 | requireNotNull(amount, "amount"); 34 | this.balance = balance.subtract(amount); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditAccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import de.wps.ddd.banking.accounting.CustomerNumber; 6 | import java.util.Objects; 7 | 8 | /** 9 | * ValueObject, representing a syntactically valid account number 10 | * 11 | *

Implemented as a class with:

12 | *
    13 | *
  • isValid method to check for validity
  • 14 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 15 | *
  • equals/hashCode based on the internal int value
  • 16 | *
17 | * 18 | * @see CustomerNumber for an alternative way of implementing value objects 19 | * @see CreditNumber for an alternative way of implementing value objects 20 | */ 21 | public class CreditAccountNumber { 22 | 23 | public static boolean isValid(int accountNumberValue) { 24 | return accountNumberValue > 0; 25 | } 26 | 27 | public static CreditAccountNumber of(int accountNumberValue) { 28 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 29 | return new CreditAccountNumber(accountNumberValue); 30 | } 31 | 32 | private final int accountNumberValue; 33 | 34 | private CreditAccountNumber(int accountNumberValue) { 35 | this.accountNumberValue = accountNumberValue; 36 | } 37 | 38 | public int valueInt() { 39 | return this.accountNumberValue; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | CreditAccountNumber that = (CreditAccountNumber) o; 47 | return accountNumberValue == that.accountNumberValue; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hash(accountNumberValue); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject representing a syntactically valid credit numbers 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 12 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditAccountNumber for an alternative way of implementing value objects 17 | */ 18 | public record CreditNumber(int creditNumberValue) { 19 | 20 | public static boolean isValid(int creditNumberValue) { 21 | return creditNumberValue > 0; 22 | } 23 | public static CreditNumber of(int creditNumberValue) { 24 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 25 | return new CreditNumber(creditNumberValue); 26 | } 27 | 28 | public int value() { 29 | return creditNumberValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import de.wps.ddd.banking.credit.CreditNumber; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | /** 9 | * Factory to create {@link CreditNumber}s. 10 | */ 11 | public class CreditNumberFactory { 12 | 13 | /** 14 | * Normally this would be backed by some kind of persistence store 15 | */ 16 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 17 | 18 | public CreditNumber newCreditNumber() { 19 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 20 | return CreditNumber.of(nextFreeNumber); 21 | } 22 | 23 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 24 | requireNotNull(creditNumber, "creditNumber"); 25 | return creditNumber.value() <= NUMBER_COUNTER.get(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/main/java/de/wps/ddd/banking/credit/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/CycleFreeWithoutSharedKernelArchTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking; 2 | 3 | import static com.tngtech.archunit.library.Architectures.layeredArchitecture; 4 | 5 | import com.tngtech.archunit.core.importer.ImportOption; 6 | import com.tngtech.archunit.junit.AnalyzeClasses; 7 | import com.tngtech.archunit.junit.ArchTest; 8 | import com.tngtech.archunit.lang.ArchRule; 9 | 10 | @AnalyzeClasses(packagesOf = CycleFreeWithoutSharedKernelArchTest.class, importOptions = { ImportOption.DoNotIncludeTests.class }) 11 | public class CycleFreeWithoutSharedKernelArchTest { 12 | 13 | @ArchTest 14 | final ArchRule boundedContexts = layeredArchitecture() 15 | .consideringOnlyDependenciesInAnyPackage(CycleFreeWithoutSharedKernelArchTest.class.getPackageName() + "..") 16 | .as("Bounded contexts") 17 | .layer("accounting").definedBy("..accounting..") 18 | .layer("credit").definedBy("..credit..") 19 | .whereLayer("accounting").mayNotBeAccessedByAnyLayer() 20 | .whereLayer("credit").mayOnlyBeAccessedByLayers("accounting") 21 | .ensureAllClassesAreContainedInArchitecture(); 22 | } 23 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | 9 | class AccountTest { 10 | 11 | public static final AccountNumber ACCOUNT_NUMBER = AccountNumber.of(9); 12 | 13 | @Test 14 | void testAccountConstruction() { 15 | 16 | Account account = new Account(ACCOUNT_NUMBER); 17 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 18 | assertEquals(0, account.getBalance().value()); 19 | } 20 | 21 | @Test 22 | void testBalanceAccount() { 23 | Account account = new Account(ACCOUNT_NUMBER); 24 | assertEquals(0, account.getBalance().value()); 25 | account.deposit(Amount.of(100)); 26 | assertEquals(100, account.getBalance().value()); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class CustomerTest { 9 | 10 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/credit/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import de.wps.ddd.banking.credit.Amount; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class AmountTest { 12 | 13 | @Test 14 | void testCreation() { 15 | assertTrue(Amount.isValidAmount(100)); 16 | assertTrue(Amount.isValidAmount(-100)); 17 | assertTrue(Amount.isValidAmount(0)); 18 | assertTrue(Amount.isValidAmount(1)); 19 | assertTrue(Amount.isValidAmount(-1)); 20 | 21 | Amount amount = Amount.of(10); 22 | assertNotNull(amount); 23 | assertEquals(10, amount.value()); 24 | } 25 | 26 | @Test 27 | void testAdd() { 28 | Amount amount1 = Amount.of(10); 29 | Amount amount2 = Amount.of(5); 30 | assertFalse(amount1.equals(amount2)); 31 | 32 | Amount amount3 = amount1.add(amount2); 33 | 34 | assertEquals(15, amount3.value()); 35 | } 36 | 37 | @Test 38 | void testSubstract() { 39 | Amount amount1 = Amount.of(10); 40 | Amount amount2 = Amount.of(5); 41 | 42 | Amount amount3 = amount1.subtract(amount2); 43 | 44 | assertEquals(5, amount3.value()); 45 | assertTrue(amount2.equals(amount3)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class CreditCustomerTest { 10 | 11 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free without sharedKernel/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class CreditTest { 8 | 9 | public static final CreditNumber CREDIT_NUMBER = CreditNumber.of(11); 10 | 11 | @Test 12 | void testCreditConstruction() { 13 | 14 | Credit credit = new Credit(CREDIT_NUMBER, Amount.of(1000)); 15 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 16 | assertEquals(CREDIT_NUMBER, credit.getCreditNumber()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/.gitignore: -------------------------------------------------------------------------------- 1 | /.classpath 2 | /.project 3 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/cycle-free.sonargraph/Analyzers/ArchitectureCheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/cycle-free.sonargraph/Architecture/BoundedContexts.arc: -------------------------------------------------------------------------------- 1 | artifact CycleFree 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | } 7 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/cycle-free.sonargraph/Architecture/BoundedContextsCheatSheet.arc: -------------------------------------------------------------------------------- 1 | artifact CycleFree 2 | { 3 | // Make sure that we do not fetch external classes 4 | exclude "External [Java]/**" 5 | 6 | artifact Accounting 7 | { 8 | include "**/accounting/**" 9 | 10 | // Allow dependencies from Accounting to Credit 11 | connect to Credit 12 | } 13 | 14 | artifact Credit 15 | { 16 | include "**/credit/**" 17 | } 18 | 19 | public artifact SharedKernel 20 | { 21 | include "**/sharedKernel/**" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/cycle-free.sonargraph/Settings/Developers.properties: -------------------------------------------------------------------------------- 1 | #Manages developer names and aliases 2 | #Fri Mar 08 14:41:35 CET 2024 3 | $Authors$=Carola Lilienthal,Remy Sanlaville,johannesrost 4 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/cycle-free.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | de.wps.ddd.banking 8 | maven-parent 9 | 1.0-SNAPSHOT 10 | ../maven-parent 11 | 12 | 13 | cycle-free 14 | 4 Hands-on Cycle-free 15 | 16 | 17 | 18 | de.wps.common 19 | common-contracts 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-api 24 | 25 | 26 | org.junit.jupiter 27 | junit-jupiter-params 28 | 29 | 30 | org.assertj 31 | assertj-core 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | com.hello2morrow 44 | sonargraph-maven-plugin 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 7 | import de.wps.ddd.banking.sharedKernel.Amount; 8 | 9 | public class Account { 10 | private final AccountNumber accountNumber; 11 | private Amount balance; 12 | 13 | public Account(AccountNumber accountNumber) { 14 | requireNotNull(accountNumber, "accountNumber"); 15 | 16 | this.accountNumber = accountNumber; 17 | this.balance = Amount.of(0); 18 | } 19 | 20 | public Amount getBalance() { 21 | return balance; 22 | } 23 | 24 | public void withdraw(Amount amount) { 25 | requireNotNull(amount, "amount"); 26 | require(amount.isLessOrEquals(getBalance()), "amount.isLessOrEquals(getBalance())"); 27 | 28 | this.balance = this.balance.subtract(amount); 29 | } 30 | 31 | public void deposit(Amount amount) { 32 | requireNotNull(amount, "amount"); 33 | 34 | this.balance = this.balance.add(amount); 35 | } 36 | 37 | public AccountNumber getAccountnumber() { 38 | return accountNumber; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import de.wps.ddd.banking.sharedKernel.Amount; 7 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 8 | import java.util.Optional; 9 | 10 | public class Credit { 11 | private final Amount amountOfCredit; 12 | private final CreditNumber creditNumber; 13 | private Status status; 14 | private CreditAccount account; 15 | 16 | public enum Status { 17 | applied, refused, granted, delayed, payed 18 | } 19 | 20 | public Credit(CreditNumber creditNumber, Amount amountOfCredit) { 21 | requireNotNull(creditNumber, "creditNumber"); 22 | requireNotNull(amountOfCredit, "amountOfCredit"); 23 | 24 | this.amountOfCredit = amountOfCredit; 25 | this.creditNumber = creditNumber; 26 | this.status = Status.applied; 27 | } 28 | 29 | public Amount getAmountOfCredit() { 30 | return amountOfCredit; 31 | } 32 | 33 | public CreditNumber getCreditNumber() { 34 | return creditNumber; 35 | } 36 | 37 | public Status getStatus() { 38 | return status; 39 | } 40 | 41 | public void grant(CreditAccount account) { 42 | requireNotNull(account, "account"); 43 | require(canBeGranted(), "canBeGranted()"); 44 | 45 | this.status = Status.granted; 46 | this.account = account; 47 | } 48 | 49 | public boolean canBeGranted() { 50 | return (this.status != Status.refused && this.status != Status.granted); 51 | } 52 | 53 | public Optional getAccount() { 54 | return Optional.ofNullable(account); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 6 | import de.wps.ddd.banking.sharedKernel.Amount; 7 | 8 | public class CreditAccount { 9 | private final AccountNumber accountNumber; 10 | private Amount balance; 11 | 12 | 13 | public CreditAccount(AccountNumber accountNumber, Amount amountOfCredit) { 14 | requireNotNull(accountNumber, "accountNumber"); 15 | requireNotNull(amountOfCredit, "amountOfCredit"); 16 | 17 | this.accountNumber = accountNumber; 18 | this.balance = Amount.of(0).subtract(amountOfCredit); 19 | } 20 | 21 | public Amount getBalance() { 22 | return balance; 23 | } 24 | 25 | public AccountNumber getAccountNumber() { 26 | return accountNumber; 27 | } 28 | 29 | public void deposit(Amount amount) { 30 | requireNotNull(amount, "amount"); 31 | this.balance = balance.add(amount); 32 | } 33 | 34 | public void withdraw(Amount amount) { 35 | requireNotNull(amount, "amount"); 36 | this.balance = balance.subtract(amount); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid account number 9 | * 10 | *

Implemented as a class with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 14 | *
  • equals/hashCode based on the internal int value
  • 15 | *
16 | * 17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see CreditNumber for an alternative way of implementing value objects 19 | */ 20 | public class AccountNumber { 21 | 22 | public static boolean isValid(int accountNumberValue) { 23 | return accountNumberValue > 0; 24 | } 25 | 26 | public static AccountNumber of(int accountNumberValue) { 27 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 28 | return new AccountNumber(accountNumberValue); 29 | } 30 | 31 | private final int accountNumberValue; 32 | 33 | private AccountNumber(int accountNumberValue) { 34 | this.accountNumberValue = accountNumberValue; 35 | } 36 | 37 | public int valueInt() { 38 | return this.accountNumberValue; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | AccountNumber that = (AccountNumber) o; 46 | return accountNumberValue == that.accountNumberValue; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(accountNumberValue); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link AccountNumber}s. 9 | */ 10 | public class AccountNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public AccountNumber newAccountNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return AccountNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 23 | requireNotNull(accountNumber, "accountNumber"); 24 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid amount 7 | * 8 | *

Implemented as a class with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 12 | *
  • equals/hashCode based on the internal int value
  • 13 | *
14 | * 15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditNumber for an alternative way of implementing value objects 17 | */ 18 | public class Amount { 19 | 20 | public static boolean isValidAmount(float amount) { 21 | // All float values are considered valid 22 | return true; 23 | } 24 | 25 | public static Amount of(float amount) { 26 | return new Amount(amount); 27 | } 28 | 29 | private final float amount; 30 | 31 | private Amount(float amount) { 32 | this.amount = amount; 33 | } 34 | 35 | public Amount add(Amount secondAmount) { 36 | return of(this.amount + secondAmount.amount); 37 | } 38 | 39 | public Amount subtract(Amount secondAmount) { 40 | return of(this.amount - secondAmount.amount); 41 | } 42 | 43 | public float value() { 44 | return this.amount; 45 | } 46 | 47 | public boolean isLessOrEquals(Amount amount) { 48 | return this.amount <= amount.value(); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | Amount amount1 = (Amount) o; 56 | return Float.compare(amount, amount1.amount) == 0; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(amount); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject representing a syntactically valid credit numbers 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 12 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see AccountNumber for an alternative way of implementing value objects 17 | */ 18 | public record CreditNumber(int creditNumberValue) { 19 | 20 | public static boolean isValid(int creditNumberValue) { 21 | return creditNumberValue > 0; 22 | } 23 | public static CreditNumber of(int creditNumberValue) { 24 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 25 | return new CreditNumber(creditNumberValue); 26 | } 27 | 28 | public int value() { 29 | return creditNumberValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CreditNumber}s. 9 | */ 10 | public class CreditNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CreditNumber newCreditNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return CreditNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 23 | requireNotNull(creditNumber, "creditNumber"); 24 | return creditNumber.value() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/main/java/de/wps/ddd/banking/sharedKernel/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CustomerNumber}s. 9 | */ 10 | public class CustomerNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CustomerNumber newCustomerNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return new CustomerNumber(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 23 | requireNotNull(CustomerNumber, "CustomerNumber"); 24 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.AccountNumber; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import de.wps.ddd.banking.sharedKernel.Amount; 9 | 10 | class AccountTest { 11 | 12 | public static final AccountNumber ACCOUNT_NUMBER = AccountNumber.of(9); 13 | 14 | @Test 15 | void testAccountConstruction() { 16 | 17 | Account account = new Account(ACCOUNT_NUMBER); 18 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 19 | assertEquals(0, account.getBalance().value()); 20 | } 21 | 22 | @Test 23 | void testBalanceAccount() { 24 | Account account = new Account(ACCOUNT_NUMBER); 25 | assertEquals(0, account.getBalance().value()); 26 | account.deposit(Amount.of(100)); 27 | assertEquals(100, account.getBalance().value()); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CustomerTest { 11 | 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 13 | 14 | @Test 15 | void testCustomerConstruction() { 16 | 17 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 18 | assertEquals("Carola", customer.getFirstName()); 19 | assertEquals("Lilienthal", customer.getFamilyName()); 20 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 21 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.CustomerNumber; 6 | import java.time.LocalDate; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class CreditCustomerTest { 11 | 12 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 13 | @Test 14 | void testCustomerConstruction() { 15 | 16 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 17 | assertEquals("Carola", customer.getFirstName()); 18 | assertEquals("Lilienthal", customer.getFamilyName()); 19 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 20 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import de.wps.ddd.banking.sharedKernel.CreditNumber; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import de.wps.ddd.banking.sharedKernel.Amount; 9 | 10 | class CreditTest { 11 | 12 | public static final CreditNumber CREDIT_NUMBER = CreditNumber.of(11); 13 | 14 | @Test 15 | void testCreditConstruction() { 16 | 17 | Credit credit = new Credit(CREDIT_NUMBER, Amount.of(1000)); 18 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 19 | assertEquals(CREDIT_NUMBER, credit.getCreditNumber()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /5 Hands-on Cycle-free/src/test/java/de/wps/ddd/banking/sharedKernel/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.sharedKernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertTrue(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertTrue(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubstract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/event-based.sonargraph/system.sonargraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/Account.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | public class Account { 7 | private final AccountNumber accountNumber; 8 | private Amount balance; 9 | 10 | public Account(AccountNumber accountNumber) { 11 | requireNotNull(accountNumber, "accountNumber"); 12 | 13 | this.accountNumber = accountNumber; 14 | this.balance = Amount.of(0); 15 | } 16 | 17 | public Amount getBalance() { 18 | return balance; 19 | } 20 | 21 | public void withdraw(Amount amount) { 22 | requireNotNull(amount, "amount"); 23 | require(amount.isLessOrEquals(getBalance()), "amount.isLessOrEquals(getBalance())"); 24 | 25 | this.balance = this.balance.subtract(amount); 26 | } 27 | 28 | public void deposit(Amount amount) { 29 | requireNotNull(amount, "amount"); 30 | 31 | this.balance = this.balance.add(amount); 32 | } 33 | 34 | public AccountNumber getAccountnumber() { 35 | return accountNumber; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/AccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * ValueObject, representing a syntactically valid account number 9 | * 10 | *

Implemented as a class with:

11 | *
    12 | *
  • isValid method to check for validity
  • 13 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 14 | *
  • equals/hashCode based on the internal int value
  • 15 | *
16 | * 17 | * @see CustomerNumber for an alternative way of implementing value objects 18 | * @see de.wps.ddd.banking.credit.CreditNumber for an alternative way of implementing value objects 19 | */ 20 | public class AccountNumber { 21 | 22 | public static boolean isValid(int accountNumberValue) { 23 | return accountNumberValue > 0; 24 | } 25 | 26 | public static AccountNumber of(int accountNumberValue) { 27 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 28 | return new AccountNumber(accountNumberValue); 29 | } 30 | 31 | private final int accountNumberValue; 32 | 33 | private AccountNumber(int accountNumberValue) { 34 | this.accountNumberValue = accountNumberValue; 35 | } 36 | 37 | public int valueInt() { 38 | return this.accountNumberValue; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | AccountNumber that = (AccountNumber) o; 46 | return accountNumberValue == that.accountNumberValue; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(accountNumberValue); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/AccountNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link AccountNumber}s. 9 | */ 10 | public class AccountNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public AccountNumber newAccountNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return AccountNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownAccountNumber(AccountNumber accountNumber) { 23 | requireNotNull(accountNumber, "accountNumber"); 24 | return accountNumber.valueInt() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import de.wps.ddd.banking.credit.CreditNumber; 4 | import java.util.Objects; 5 | 6 | /** 7 | * ValueObject, representing a syntactically valid amount 8 | * 9 | *

Implemented as a class with:

10 | *
    11 | *
  • isValid method to check for validity
  • 12 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * 16 | * @see CustomerNumber for an alternative way of implementing value objects 17 | * @see CreditNumber for an alternative way of implementing value objects 18 | */ 19 | public class Amount { 20 | 21 | public static boolean isValidAmount(float amount) { 22 | // All float values are considered valid 23 | return true; 24 | } 25 | 26 | public static Amount of(float amount) { 27 | return new Amount(amount); 28 | } 29 | 30 | private final float amount; 31 | 32 | private Amount(float amount) { 33 | this.amount = amount; 34 | } 35 | 36 | public Amount add(Amount secondAmount) { 37 | return of(this.amount + secondAmount.amount); 38 | } 39 | 40 | public Amount subtract(Amount secondAmount) { 41 | return of(this.amount - secondAmount.amount); 42 | } 43 | 44 | public float value() { 45 | return this.amount; 46 | } 47 | 48 | public boolean isLessOrEquals(Amount amount) { 49 | return this.amount <= amount.value(); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) return true; 55 | if (o == null || getClass() != o.getClass()) return false; 56 | Amount amount1 = (Amount) o; 57 | return Float.compare(amount, amount1.amount) == 0; 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(amount); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see de.wps.ddd.banking.credit.CreditNumber 19 | * @see AccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/CustomerNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CustomerNumber}s. 9 | */ 10 | public class CustomerNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CustomerNumber newCustomerNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return new CustomerNumber(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCustomerNumber(CustomerNumber CustomerNumber) { 23 | requireNotNull(CustomerNumber, "CustomerNumber"); 24 | return CustomerNumber.customerNumberValue() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accounting/CustomerRegistrationEventPublisher.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import de.wps.ddd.banking.accountingevents.NewCustomerRegisteredEvent; 4 | import java.util.function.Consumer; 5 | 6 | public class CustomerRegistrationEventPublisher { 7 | 8 | private final Consumer eventBus; 9 | 10 | public CustomerRegistrationEventPublisher() { 11 | this(e -> {}); 12 | } 13 | 14 | CustomerRegistrationEventPublisher(Consumer eventBus) { 15 | this.eventBus = eventBus; 16 | } 17 | 18 | 19 | public void newCustomerRegistered(Customer customer) { 20 | 21 | eventBus.accept(new NewCustomerRegisteredEvent(customer.getCustomerNumber().customerNumberValue(), 22 | customer.getFirstName(), 23 | customer.getFamilyName(), 24 | customer.getDateOfBirth())); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/accountingevents/NewCustomerRegisteredEvent.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accountingevents; 2 | 3 | import java.time.LocalDate; 4 | 5 | public record NewCustomerRegisteredEvent( 6 | int customerNumber, 7 | String firstName, 8 | String familyName, 9 | LocalDate dateOfBirth 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/Amount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid amount 7 | * 8 | *

Implemented as a class with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 12 | *
  • equals/hashCode based on the internal int value
  • 13 | *
14 | * 15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditNumber for an alternative way of implementing value objects 17 | */ 18 | public class Amount { 19 | 20 | public static boolean isValidAmount(float amount) { 21 | // All float values are considered valid 22 | return true; 23 | } 24 | 25 | public static Amount of(float amount) { 26 | return new Amount(amount); 27 | } 28 | 29 | private final float amount; 30 | 31 | private Amount(float amount) { 32 | this.amount = amount; 33 | } 34 | 35 | public Amount add(Amount secondAmount) { 36 | return of(this.amount + secondAmount.amount); 37 | } 38 | 39 | public Amount subtract(Amount secondAmount) { 40 | return of(this.amount - secondAmount.amount); 41 | } 42 | 43 | public float value() { 44 | return this.amount; 45 | } 46 | 47 | public boolean isLessOrEquals(Amount amount) { 48 | return this.amount <= amount.value(); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | Amount amount1 = (Amount) o; 56 | return Float.compare(amount, amount1.amount) == 0; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(amount); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 5 | 6 | import java.util.Optional; 7 | 8 | public class Credit { 9 | private final Amount amountOfCredit; 10 | private final CreditNumber creditNumber; 11 | private Status status; 12 | private CreditAccount account; 13 | 14 | public enum Status { 15 | applied, refused, granted, delayed, payed 16 | } 17 | 18 | public Credit(CreditNumber creditNumber, Amount amountOfCredit) { 19 | requireNotNull(creditNumber, "creditNumber"); 20 | requireNotNull(amountOfCredit, "amountOfCredit"); 21 | 22 | this.amountOfCredit = amountOfCredit; 23 | this.creditNumber = creditNumber; 24 | this.status = Status.applied; 25 | } 26 | 27 | public Amount getAmountOfCredit() { 28 | return amountOfCredit; 29 | } 30 | 31 | public CreditNumber getCreditNumber() { 32 | return creditNumber; 33 | } 34 | 35 | public Status getStatus() { 36 | return status; 37 | } 38 | 39 | public void grant(CreditAccount account) { 40 | requireNotNull(account, "account"); 41 | require(canBeGranted(), "canBeGranted()"); 42 | 43 | this.status = Status.granted; 44 | this.account = account; 45 | } 46 | 47 | public boolean canBeGranted() { 48 | return (this.status != Status.refused && this.status != Status.granted); 49 | } 50 | 51 | public Optional getAccount() { 52 | return Optional.ofNullable(account); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | 6 | public class CreditAccount { 7 | private final CreditAccountNumber creditAccountNumber; 8 | private Amount balance; 9 | 10 | 11 | public CreditAccount(CreditAccountNumber creditAccountNumber, Amount amountOfCredit) { 12 | requireNotNull(creditAccountNumber, "accountNumber"); 13 | requireNotNull(amountOfCredit, "amountOfCredit"); 14 | 15 | this.creditAccountNumber = creditAccountNumber; 16 | this.balance = Amount.of(0).subtract(amountOfCredit); 17 | } 18 | 19 | public Amount getBalance() { 20 | return balance; 21 | } 22 | 23 | public CreditAccountNumber getAccountNumber() { 24 | return creditAccountNumber; 25 | } 26 | 27 | public void deposit(Amount amount) { 28 | requireNotNull(amount, "amount"); 29 | this.balance = balance.add(amount); 30 | } 31 | 32 | public void withdraw(Amount amount) { 33 | requireNotNull(amount, "amount"); 34 | this.balance = balance.subtract(amount); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CreditAccountNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | import de.wps.ddd.banking.accounting.CustomerNumber; 6 | import java.util.Objects; 7 | 8 | /** 9 | * ValueObject, representing a syntactically valid account number 10 | * 11 | *

Implemented as a class with:

12 | *
    13 | *
  • isValid method to check for validity
  • 14 | *
  • private constructor and a factory method "of" to control object creation and decouple external and internal representation
  • 15 | *
  • equals/hashCode based on the internal int value
  • 16 | *
17 | * 18 | * @see CustomerNumber for an alternative way of implementing value objects 19 | * @see CreditNumber for an alternative way of implementing value objects 20 | */ 21 | public class CreditAccountNumber { 22 | 23 | public static boolean isValid(int accountNumberValue) { 24 | return accountNumberValue > 0; 25 | } 26 | 27 | public static CreditAccountNumber of(int accountNumberValue) { 28 | require(isValid(accountNumberValue), "isValid(accountNumberValue)"); 29 | return new CreditAccountNumber(accountNumberValue); 30 | } 31 | 32 | private final int accountNumberValue; 33 | 34 | private CreditAccountNumber(int accountNumberValue) { 35 | this.accountNumberValue = accountNumberValue; 36 | } 37 | 38 | public int valueInt() { 39 | return this.accountNumberValue; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | CreditAccountNumber that = (CreditAccountNumber) o; 47 | return accountNumberValue == that.accountNumberValue; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hash(accountNumberValue); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CreditNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject representing a syntactically valid credit numbers 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check for validity
  • 11 | *
  • a factory method "of" to try to control object creation and decouple external and internal representation
  • 12 | *
  • public default record constructor, which must not be used directly, see ArchUnit-Test
  • 13 | *
  • equals/hashCode based on the internal int value
  • 14 | *
15 | * @see CustomerNumber for an alternative way of implementing value objects 16 | * @see CreditAccountNumber for an alternative way of implementing value objects 17 | */ 18 | public record CreditNumber(int creditNumberValue) { 19 | 20 | public static boolean isValid(int creditNumberValue) { 21 | return creditNumberValue > 0; 22 | } 23 | public static CreditNumber of(int creditNumberValue) { 24 | require(isValid(creditNumberValue), "isValid(creditNumberValue)"); 25 | return new CreditNumber(creditNumberValue); 26 | } 27 | 28 | public int value() { 29 | return creditNumberValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CreditNumberFactory.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.requireNotNull; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Factory to create {@link CreditNumber}s. 9 | */ 10 | public class CreditNumberFactory { 11 | 12 | /** 13 | * Normally this would be backed by some kind of persistence store 14 | */ 15 | private static final AtomicInteger NUMBER_COUNTER = new AtomicInteger(0); 16 | 17 | public CreditNumber newCreditNumber() { 18 | int nextFreeNumber = NUMBER_COUNTER.incrementAndGet(); 19 | return CreditNumber.of(nextFreeNumber); 20 | } 21 | 22 | public boolean isKnownCreditNumber(CreditNumber creditNumber) { 23 | requireNotNull(creditNumber, "creditNumber"); 24 | return creditNumber.value() <= NUMBER_COUNTER.get(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CustomerNumber.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static de.wps.common.contracts.BaseContracts.require; 4 | 5 | /** 6 | * ValueObject, representing a syntactically valid customer number 7 | * 8 | *

Implemented as a record with:

9 | *
    10 | *
  • isValid method to check validity
  • 11 | *
  • a public constructor directly coupled to the internal representation
  • 12 | *
  • validation implemented in the compact constructor
  • 13 | *
  • default method to access the internal representation
  • 14 | *
  • equals/hashCode automatically based on the internal int value
  • 15 | *
16 | * 17 | * @param customerNumberValue internal value of the customer number 18 | * @see CreditNumber 19 | * @see CreditAccountNumber 20 | */ 21 | public record CustomerNumber(int customerNumberValue) { 22 | public CustomerNumber { 23 | require(isValid(customerNumberValue), "isValid(customerNumberValue)"); 24 | } 25 | 26 | public static boolean isValid(int customerNumberValue) { 27 | return customerNumberValue > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/main/java/de/wps/ddd/banking/credit/CustomerRegistrationEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | 4 | import de.wps.ddd.banking.accountingevents.NewCustomerRegisteredEvent; 5 | 6 | public class CustomerRegistrationEventHandler { 7 | 8 | private final CreditService creditService; 9 | 10 | public CustomerRegistrationEventHandler(CreditService creditService) { 11 | this.creditService = creditService; 12 | } 13 | 14 | void handle(NewCustomerRegisteredEvent newCustomer) { 15 | CustomerNumber customerNumber = new CustomerNumber(newCustomer.customerNumber()); 16 | creditService.newCustomer(newCustomer.firstName(), newCustomer.familyName(), newCustomer.dateOfBirth(), customerNumber); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/CycleFreeWithoutSharedKernelArchTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking; 2 | 3 | import static com.tngtech.archunit.library.Architectures.layeredArchitecture; 4 | 5 | import com.tngtech.archunit.core.importer.ImportOption; 6 | import com.tngtech.archunit.junit.AnalyzeClasses; 7 | import com.tngtech.archunit.junit.ArchTest; 8 | import com.tngtech.archunit.lang.ArchRule; 9 | 10 | @AnalyzeClasses(packagesOf = CycleFreeWithoutSharedKernelArchTest.class, importOptions = { ImportOption.DoNotIncludeTests.class }) 11 | public class CycleFreeWithoutSharedKernelArchTest { 12 | 13 | @ArchTest 14 | final ArchRule boundedContexts = layeredArchitecture() 15 | .consideringOnlyDependenciesInAnyPackage(CycleFreeWithoutSharedKernelArchTest.class.getPackageName() + "..") 16 | .as("Bounded contexts") 17 | .layer("accounting").definedBy("..accounting..") 18 | .layer("accountingevents").definedBy("..accountingevents..") 19 | .layer("credit").definedBy("..credit..") 20 | .whereLayer("accounting").mayNotBeAccessedByAnyLayer() 21 | .whereLayer("credit").mayNotBeAccessedByAnyLayer() 22 | .whereLayer("accountingevents").mayOnlyBeAccessedByLayers("accounting", "credit") 23 | .ensureAllClassesAreContainedInArchitecture(); 24 | } 25 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/accounting/AccountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | 9 | class AccountTest { 10 | 11 | public static final AccountNumber ACCOUNT_NUMBER = AccountNumber.of(9); 12 | 13 | @Test 14 | void testAccountConstruction() { 15 | 16 | Account account = new Account(ACCOUNT_NUMBER); 17 | assertEquals(ACCOUNT_NUMBER, account.getAccountnumber()); 18 | assertEquals(0, account.getBalance().value()); 19 | } 20 | 21 | @Test 22 | void testBalanceAccount() { 23 | Account account = new Account(ACCOUNT_NUMBER); 24 | assertEquals(0, account.getBalance().value()); 25 | account.deposit(Amount.of(100)); 26 | assertEquals(100, account.getBalance().value()); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/accounting/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.accounting; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class CustomerTest { 9 | 10 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 11 | 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | Customer customer = new Customer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/credit/AmountTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AmountTest { 11 | 12 | @Test 13 | void testCreation() { 14 | assertTrue(Amount.isValidAmount(100)); 15 | assertTrue(Amount.isValidAmount(-100)); 16 | assertTrue(Amount.isValidAmount(0)); 17 | assertTrue(Amount.isValidAmount(1)); 18 | assertTrue(Amount.isValidAmount(-1)); 19 | 20 | Amount amount = Amount.of(10); 21 | assertNotNull(amount); 22 | assertEquals(10, amount.value()); 23 | } 24 | 25 | @Test 26 | void testAdd() { 27 | Amount amount1 = Amount.of(10); 28 | Amount amount2 = Amount.of(5); 29 | assertFalse(amount1.equals(amount2)); 30 | 31 | Amount amount3 = amount1.add(amount2); 32 | 33 | assertEquals(15, amount3.value()); 34 | } 35 | 36 | @Test 37 | void testSubstract() { 38 | Amount amount1 = Amount.of(10); 39 | Amount amount2 = Amount.of(5); 40 | 41 | Amount amount3 = amount1.subtract(amount2); 42 | 43 | assertEquals(5, amount3.value()); 44 | assertTrue(amount2.equals(amount3)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/credit/CreditCustomerTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class CreditCustomerTest { 10 | 11 | public static final CustomerNumber CUSTOMER_NUMBER = new CustomerNumber(1); 12 | @Test 13 | void testCustomerConstruction() { 14 | 15 | CreditCustomer customer = new CreditCustomer(CUSTOMER_NUMBER, "Carola", "Lilienthal", LocalDate.of(1967, 9, 11)); 16 | assertEquals("Carola", customer.getFirstName()); 17 | assertEquals("Lilienthal", customer.getFamilyName()); 18 | assertEquals(LocalDate.of(1967, 9, 11), customer.getDateOfBirth()); 19 | assertEquals(CUSTOMER_NUMBER, customer.getCustomerNumber()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /6 Hands-on Event Based/src/test/java/de/wps/ddd/banking/credit/CreditTest.java: -------------------------------------------------------------------------------- 1 | package de.wps.ddd.banking.credit; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class CreditTest { 8 | 9 | public static final CreditNumber CREDIT_NUMBER = CreditNumber.of(11); 10 | 11 | @Test 12 | void testCreditConstruction() { 13 | 14 | Credit credit = new Credit(CREDIT_NUMBER, Amount.of(1000)); 15 | assertEquals(Amount.of(1000), credit.getAmountOfCredit()); 16 | assertEquals(CREDIT_NUMBER, credit.getCreditNumber()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Carola Lilienthal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddd-banking-example 2 | 3 | ## Build and Test 4 | 5 | On Windows: 6 | 7 | ```powershell 8 | cd "1 Hands-on Legacy Code" 9 | ./mvnw.cmd package 10 | ``` 11 | 12 | On Unix/Linux/MacOS: 13 | 14 | ```sh 15 | cd "1 Hands-on Legacy Code" 16 | ./mvnw package 17 | ``` 18 | 19 | ## Containerized Version 20 | 21 | The easiest way is to use open the project in Visual Studio Code inside a Devcontainer. For that you need Docker installed. 22 | 23 | ### Install Docker Desktop 24 | 25 | On Windows: 26 | 27 | ```powershell 28 | winget install -e --id Docker.DockerDesktop 29 | ``` 30 | 31 | On MacOS: 32 | 33 | ```sh 34 | brew install --cask docker 35 | ``` 36 | 37 | ## On Your Own System 38 | 39 | You need a JDK version 21. 40 | 41 | ### Install prerequisites 42 | 43 | On Windows: 44 | 45 | ```powershell 46 | winget install -e --id EclipseAdoptium.Temurin.21.JDK 47 | ``` 48 | 49 | On MacOS: 50 | 51 | ```sh 52 | brew install temurin@21 53 | ``` 54 | --------------------------------------------------------------------------------