Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Value and Record Types

Value and Record Types

Slides from my talk “Better Domain Models with Value and Record Types.”

Henning Schwentner

September 12, 2023
Tweet

More Decks by Henning Schwentner

Other Decks in Programming

Transcript

  1. Foto: P. Campbell/Wikipedia James Gosling Everything is an object int

    short long byte char float double boolean … except:
  2. '

  3. ' '

  4. 3

  5. 3 3

  6. 3

  7. @Entity public class Account { private int _balance; public int

    getBalance() { return _balance; } public void setBalance(int balance) { _balance = balance; } } ✘ Bad: The account balance can be set to any value
  8. @Entity public class Account { private int _balance; public int

    balance() { return _balance; } public void deposit(int amount) { _balance += amount; } public void withdraw(int amount) { _balance -= amount; } Better: Operations with domain- specific behavior and names
  9. @Entity public class Account { private int _balance; public int

    balance() { return _balance; } public void deposit(int amount) { _balance += amount; } public void withdraw(int amount) { if (amount > balance()) { throw new IllegalArgumentException("Amount too big"); Even better: Preconditions can be checked
  10. @Entity public class Account { // ... public void withdraw(int

    amount) { assert amount <= balance(); _balance -= amount; } } Assertion using keyword assert
  11. GREAT, BUT… @Entity public class Account { // ... public

    void withdraw(int amount) { assert amount <= balance(); _balance -= amount; } } Can I withdraw a negative amount? In EUR or GBP or…?
  12. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  13. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; private Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public static Amount of(int amount, Currency currency) { return new Amount(amount, currency); } }
  14. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } @Override public boolean equals(Object other) { return _amount == ((Amount) other)._amount && _currency.equals(((Amount) other)._currency); }
  15. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } @Override public boolean equals(Object other) { return _amount == ((Amount) other)._amount && _currency.equals(((Amount) other)._currency); }
  16. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount otherAmount) { return Amount.of(_amount + otherAmount._amount, _currency); } } Der neue Typ hat richtiges fachliches Verhalten
  17. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount otherAmount) { assert hasSameCurrency(otherAmount); return Amount.of(_amount + otherAmount._amount, _currency); } … und Verträge, die vor falschen Währungen schützen
  18. @ValueObject public record Amount(int amount, Currency currency) { public Amount

    add(Amount otherAmount) { assert hasSameCurrency(otherAmount); return new Amount(amount + otherAmount.amount(), currency); } public boolean hasSameCurrency(Amount otherAmount) { return otherAmount.currency() == currency; } }
  19. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  20. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) { return other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); } }
  21. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) => other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); }
  22. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) => other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); // GetHashCode() }
  23. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public static bool operator ==(Amount a, Amount b) => a.Equals(b); }
  24. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public static bool operator ==(Amount a, SignDate b) => a.Equals(b); public static bool operator !=(Amount a, SignDate b) => !a.Equals(b); }
  25. 3

  26. 3

  27. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  28. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // Equals() does not have to be overridden }
  29. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // but the operators have to be overridden }
  30. [ValueObject] public record Amount(int amount, Currency currency); // automatically readonly

    // Equals(), GetHashCode(), ToString(), operators // do not have to be overloaded and work as expected
  31. But

  32. 3 3

  33. 3

  34. 3

  35. 3

  36. 3

  37. @hschwentner Key take aways: *value/object are different *only records yet

    * value types are still to come (*domain model and performance)
  38. Bibliography Beck, Kent et al. Manifesto for Agile Software Development.

    2001. Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Boston: Addison-Wesley, 2004. Hofer, Stefan and Henning Schwentner. Domain Storytelling: a Collaborative, Visual, and Agile Way to Develop Domain- Driven Software. Boston: Addison-Wesley, 2022.