Skip to content

Commit f01edb1

Browse files
committed
Finish port of "3 Hands-On Value Objects"
1 parent 12db02b commit f01edb1

16 files changed

Lines changed: 300 additions & 136 deletions

File tree

.github/workflows/build.yml

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
1-
name: DDD banking example in C# - CI
2-
3-
on: [ push, pull_request ]
4-
5-
jobs:
6-
build:
7-
8-
runs-on: ubuntu-24.04
9-
10-
steps:
11-
- name: Checkout source code
12-
uses: actions/checkout@v4
13-
14-
- name: Setup .NET Core
15-
uses: actions/setup-dotnet@v4
16-
with:
17-
dotnet-version: '8.0.x'
18-
19-
- name: Install dependencies, build, and test 1
20-
working-directory: "./1 Hands-on Legacy Code"
21-
run: |
22-
dotnet restore
23-
dotnet build --configuration Release --no-restore
24-
dotnet test --no-restore --verbosity normal
25-
26-
- name: Install dependencies, build, and test 1
27-
working-directory: "./2 Hands-on Bounded Contexts"
28-
run: |
29-
dotnet restore
30-
dotnet build --configuration Release --no-restore
31-
dotnet test --no-restore --verbosity normal
32-
33-
- name: Install dependencies, build, and test 1
34-
working-directory: "./3 Hands-on Value Objects"
35-
run: |
36-
dotnet restore
37-
dotnet build --configuration Release --no-restore
38-
dotnet test --no-restore --verbosity normal
1+
name: DDD banking example in C# - CI
2+
3+
on: [ push, pull_request ]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-24.04
9+
10+
steps:
11+
- name: Checkout source code
12+
uses: actions/checkout@v4
13+
14+
- name: Setup .NET Core
15+
uses: actions/setup-dotnet@v4
16+
with:
17+
dotnet-version: '8.0.x'
18+
19+
- name: Install dependencies, build, and test 1
20+
working-directory: "./1 Hands-on Legacy Code"
21+
run: |
22+
dotnet restore
23+
dotnet build --configuration Release --no-restore
24+
dotnet test --no-restore --verbosity normal
25+
26+
- name: Install dependencies, build, and test 1
27+
working-directory: "./2 Hands-on Bounded Contexts"
28+
run: |
29+
dotnet restore
30+
dotnet build --configuration Release --no-restore
31+
dotnet test --no-restore --verbosity normal
32+
33+
- name: Install dependencies, build, and test 1
34+
working-directory: "./3 Hands-on Value Objects"
35+
run: |
36+
dotnet restore
37+
dotnet build --configuration Release --no-restore
38+
dotnet test --no-restore --verbosity normal

3 Hands-on Value Objects/src/Banking/Accounting/Customer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public Customer(String firstName, String familyName, DateOnly dateOfBirth, int c
1818
this.dateOfBirth = dateOfBirth;
1919
this.customerNumber = customerNumber;
2020
accountList = new List<Account>();
21-
creditList = new IList<Credit>();
21+
creditList = new List<Credit>();
2222
}
2323

2424
public String GetFirstName()

3 Hands-on Value Objects/src/Banking/Crediting/CreditCustomer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public CreditCustomer(CustomerNumber customerNumber, String firstName, String fa
1616
this.familyName = familyName;
1717
this.dateOfBirth = dateOfBirth;
1818
this.customerNumber = customerNumber;
19-
accountList = new List<>();
20-
creditList = new List<>();
19+
accountList = new List<CreditAccount>();
20+
creditList = new List<Credit>();
2121
}
2222

2323
public String getFirstName()
Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
1-
namespace Banking.SharedKernel;
2-
3-
/**
4-
* ValueObject, representing a syntactically valid account number
5-
*
6-
* <p>Implemented as a class with:</p>
7-
* <ul>
8-
* <li>isValid method to check for validity</li>
9-
* <li>private constructor and a factory method "of" to control object creation and decouple external and internal representation</li>
10-
* <li>equals/hashCode based on the internal int value</li>
11-
* </ul>
12-
*
13-
* @see CustomerNumber for an alternative way of implementing value objects
14-
* @see CreditNumber for an alternative way of implementing value objects
15-
*/
16-
public class AccountNumber
17-
{
18-
19-
public static bool IsValid(int accountNumberValue) {
20-
return accountNumberValue > 0;
21-
}
22-
23-
public static AccountNumber Of(int accountNumberValue) {
24-
return new AccountNumber(accountNumberValue);
25-
}
26-
27-
private int accountNumberValue;
28-
29-
private AccountNumber(int accountNumberValue) {
30-
this.accountNumberValue = accountNumberValue;
31-
}
32-
33-
public int ValueInt() {
34-
return this.accountNumberValue;
35-
}
36-
37-
}
1+
namespace Banking.SharedKernel;
2+
3+
/**
4+
* ValueObject, representing a syntactically valid account number
5+
*
6+
* <p>Implemented as a class with:</p>
7+
* <ul>
8+
* <li>isValid method to check for validity</li>
9+
* <li>private constructor and a factory method "of" to control object creation and decouple external and internal representation</li>
10+
* <li>equals/hashCode based on the internal int value</li>
11+
* </ul>
12+
*
13+
* @see CustomerNumber for an alternative way of implementing value objects
14+
* @see CreditNumber for an alternative way of implementing value objects
15+
*/
16+
public class AccountNumber
17+
{
18+
19+
public static bool IsValid(int accountNumberValue)
20+
{
21+
return accountNumberValue > 0;
22+
}
23+
24+
public static AccountNumber Of(int accountNumberValue)
25+
{
26+
return new AccountNumber(accountNumberValue);
27+
}
28+
29+
private int accountNumberValue;
30+
31+
private AccountNumber(int accountNumberValue)
32+
{
33+
this.accountNumberValue = accountNumberValue;
34+
}
35+
36+
public int ValueInt()
37+
{
38+
return this.accountNumberValue;
39+
}
40+
41+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace Banking.SharedKernel;
2+
3+
using static System.Diagnostics.Debug;
4+
5+
/**
6+
* Factory to create {@link AccountNumber}s.
7+
*/
8+
public class AccountNumberFactory
9+
{
10+
/**
11+
* Normally this would be backed by some kind of persistence store
12+
*/
13+
private static int numberCounter;
14+
15+
public AccountNumber NewAccountNumber()
16+
{
17+
int nextFreeNumber = Interlocked.Increment(ref numberCounter);
18+
return AccountNumber.Of(nextFreeNumber);
19+
}
20+
21+
public bool IsKnownAccountNumber(AccountNumber accountNumber)
22+
{
23+
Assert(accountNumber != null);
24+
return accountNumber.ValueInt() <= numberCounter;
25+
}
26+
}

3 Hands-on Value Objects/src/Banking/SharedKernel/Amount.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Banking.SharedKernel;
1616
public class Amount
1717
{
1818

19-
public static bool IsValidAmount(Amount amount)
19+
public static bool IsValidAmount(float amount)
2020
{
2121
// All float values are considered valid
2222
return true;
@@ -49,4 +49,17 @@ public float Value()
4949
return this.amount;
5050
}
5151

52+
public override bool Equals(object? obj)
53+
{
54+
if (ReferenceEquals(this, obj)) return true;
55+
if (obj == null || GetType() != obj.GetType()) return false;
56+
57+
var other = (Amount)obj;
58+
return amount == other.amount;
59+
}
60+
61+
public override int GetHashCode()
62+
{
63+
return HashCode.Combine(amount);
64+
}
5265
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace Banking.SharedKernel;
2+
3+
using static System.Diagnostics.Debug;
4+
5+
/**
6+
* ValueObject representing a syntactically valid credit numbers
7+
*
8+
* <p>Implemented as a record with:</p>
9+
* <ul>
10+
* <li>isValid method to check for validity</li>
11+
* <li>a factory method "of" to try to control object creation and decouple external and internal representation</li>
12+
* <li>public default record constructor, which must not be used directly, see ArchUnit-Test</li>
13+
* <li>equals/hashCode based on the internal int value</li>
14+
* </ul>
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 bool IsValid(int creditNumberValue)
21+
{
22+
return creditNumberValue > 0;
23+
}
24+
25+
public static CreditNumber Of(int creditNumberValue)
26+
{
27+
Assert(IsValid(creditNumberValue));
28+
return new CreditNumber(creditNumberValue);
29+
}
30+
31+
public int Value()
32+
{
33+
return CreditNumberValue;
34+
}
35+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace Banking.SharedKernel;
2+
3+
using static System.Diagnostics.Debug;
4+
5+
/**
6+
* Factory to create {@link CreditNumber}s.
7+
*/
8+
public class CreditNumberFactory
9+
{
10+
/**
11+
* Normally this would be backed by some kind of persistence store
12+
*/
13+
private static int numberCounter = 0;
14+
15+
public CreditNumber NewCreditNumber()
16+
{
17+
int nextFreeNumber = Interlocked.Increment(ref numberCounter);
18+
return CreditNumber.Of(nextFreeNumber);
19+
}
20+
21+
public bool IsKnownCreditNumber(CreditNumber creditNumber)
22+
{
23+
Assert(creditNumber != null);
24+
return creditNumber.Value() <= numberCounter;
25+
}
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace Banking.SharedKernel;
2+
3+
using static System.Diagnostics.Debug;
4+
5+
/**
6+
* ValueObject, representing a syntactically valid customer number
7+
*
8+
* <p>Implemented as a record with:</p>
9+
* <ul>
10+
* <li>isValid method to check validity</li>
11+
* <li>a public constructor directly coupled to the internal representation</li>
12+
* <li>validation implemented in the compact constructor</li>
13+
* <li>default method to access the internal representation</li>
14+
* <li>equals/hashCode automatically based on the internal int value</li>
15+
* </ul>
16+
*
17+
* @param customerNumberValue internal value of the customer number
18+
* @see CreditNumber
19+
* @see AccountNumber
20+
*/
21+
public readonly record struct CustomerNumber(int CustomerNumberValue)
22+
{
23+
public static CustomerNumber Of(int customerNumberValue)
24+
{
25+
Assert(IsValid(customerNumberValue));
26+
return new CustomerNumber(customerNumberValue);
27+
}
28+
29+
public static bool IsValid(int customerNumberValue)
30+
{
31+
return customerNumberValue > 0;
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Banking.SharedKernel;
2+
3+
using static System.Diagnostics.Debug;
4+
5+
/**
6+
* Factory to create {@link CustomerNumber}s.
7+
*/
8+
public class CustomerNumberFactory
9+
{
10+
11+
/**
12+
* Normally this would be backed by some kind of persistence store
13+
*/
14+
private static int numberCounter = 0;
15+
16+
public CustomerNumber NewCustomerNumber()
17+
{
18+
int nextFreeNumber = Interlocked.Increment(ref numberCounter);
19+
return new CustomerNumber(nextFreeNumber);
20+
}
21+
22+
public bool IsKnownCustomerNumber(CustomerNumber CustomerNumber)
23+
{
24+
Assert(CustomerNumber != null);
25+
return CustomerNumber.CustomerNumberValue <= numberCounter;
26+
}
27+
}

0 commit comments

Comments
 (0)