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 |
--------------------------------------------------------------------------------